Ràng buộc kiểu nhiều phương thức chung (OR)


135

Đọc điều này , tôi đã học được rằng có thể cho phép một phương thức chấp nhận các tham số của nhiều loại bằng cách biến nó thành một phương thức chung. Trong ví dụ này, đoạn mã sau được sử dụng với ràng buộc kiểu để đảm bảo "U" là một IEnumerable<T>.

public T DoSomething<U, T>(U arg) where U : IEnumerable<T>
{
    return arg.First();
}

Tôi đã tìm thấy một số mã cho phép thêm nhiều ràng buộc kiểu, chẳng hạn như:

public void test<T>(string a, T arg) where T: ParentClass, ChildClass 
{
    //do something
}

Tuy nhiên, mã này dường như bắt buộc argphải là cả một loại ParentClass ChildClass . Những gì tôi muốn làm là nói rằng arg có thể là một loại ParentClass hoặc ChildClass theo cách sau:

public void test<T>(string a, T arg) where T: string OR Exception
{
//do something
}

Sự giúp đỡ của bạn được đánh giá cao như mọi khi!


4
Bạn có thể làm gì một cách hữu ích, theo cách chung trong cơ thể của phương thức đó (trừ khi nhiều loại tất cả xuất phát từ một lớp cơ sở cụ thể, trong trường hợp tại sao không khai báo đó là ràng buộc kiểu)?
Damien_The_Unbeliever

@Damien_The_Unbeliever Không chắc bạn có ý gì trong cơ thể? Trừ khi bạn có nghĩa là cho phép bất kỳ loại nào và kiểm tra thủ công trong phần thân ... và trong mã thực tế tôi muốn viết (đoạn mã cuối cùng), tôi muốn có thể truyền một chuỗi HOẶC ngoại lệ, vì vậy không có lớp nào mối quan hệ ở đó (ngoại trừ system.object tôi tưởng tượng).
Mansfield

1
Cũng lưu ý rằng không có sử dụng bằng văn bản where T : string, như stringlà một lớp kín . Điều hữu ích duy nhất bạn có thể làm là xác định tình trạng quá tải cho stringT : Exception, như được giải thích bởi @ Botz3000 trong câu trả lời của ông dưới đây.
Mattias Buelens

Nhưng khi không có mối quan hệ nào, các phương thức duy nhất bạn có thể gọi arglà các phương thức được xác định bởiobject - vậy tại sao không loại bỏ khái quát khỏi hình ảnh và tạo kiểu arg object? Bạn đang đạt được gì?
Damien_The_Unbeliever

1
@Mansfield Bạn có thể tạo một phương thức riêng chấp nhận một tham số đối tượng. Cả hai quá tải sẽ gọi đó là một. Không có thuốc generic cần thiết ở đây.
Botz3000

Câu trả lời:


68

Đó là không thể. Tuy nhiên, bạn có thể xác định tình trạng quá tải cho các loại cụ thể:

public void test(string a, string arg);
public void test(string a, Exception arg);

Nếu chúng là một phần của lớp chung, chúng sẽ được ưu tiên hơn phiên bản chung của phương thức.


1
Hấp dẫn. Điều này đã xảy ra với tôi, nhưng tôi nghĩ nó có thể làm cho mã sạch hơn để sử dụng một hàm. À, cảm ơn rất nhiều! Vì tò mò, bạn có biết nếu có một lý do cụ thể thì điều này là không thể? (nó có bị bỏ ngoài ngôn ngữ một cách có chủ ý không?)
Mansfield

3
@Mansfield Tôi không biết lý do chính xác, nhưng tôi nghĩ bạn sẽ không thể sử dụng các tham số chung theo cách có ý nghĩa nữa. Trong lớp, họ sẽ phải được đối xử như đối tượng nếu họ được phép thuộc các loại hoàn toàn khác nhau. Điều đó có nghĩa là bạn cũng có thể bỏ qua tham số chung và cung cấp quá tải.
Botz3000

3
@Mansfield, đó là vì một ormối quan hệ làm cho mọi thứ trở nên chung chung trở nên hữu ích. Bạn sẽ phải suy ngẫm để tìm ra phải làm gì và tất cả những thứ đó. (KINH QUÁ!).
Chris Pfohl

29

Botz trả lời đúng 100%, đây là một lời giải thích ngắn:

Khi bạn đang viết một phương thức (chung chung hoặc không) và khai báo các loại tham số mà phương thức đưa bạn đang xác định hợp đồng:

Nếu bạn đưa cho tôi một đối tượng biết cách thực hiện tập hợp những thứ mà Loại T biết cách thực hiện, tôi có thể cung cấp 'a': giá trị trả về của loại tôi khai báo hoặc 'b': một số loại hành vi sử dụng kiểu đó

Nếu bạn thử và cung cấp cho nó nhiều loại cùng một lúc (bằng cách có hoặc) hoặc cố gắng lấy nó để trả về một giá trị có thể nhiều hơn một loại mà hợp đồng trở nên mờ nhạt:

Nếu bạn đưa cho tôi một vật thể biết nhảy dây hoặc biết cách tính số pi đến chữ số thứ 15, tôi sẽ trả lại một vật có thể đi câu cá hoặc có thể trộn bê tông.

Vấn đề là khi bạn tham gia vào phương pháp, bạn không biết họ đã cho bạn một IJumpRopehay a PiFactory. Hơn nữa, khi bạn tiếp tục và sử dụng phương pháp (giả sử rằng bạn đã nhận được nó để biên dịch một cách kỳ diệu), bạn không thực sự chắc chắn nếu bạn có một Fisherhoặc một AbstractConcreteMixer. Về cơ bản nó làm cho toàn bộ cách khó hiểu hơn.

Giải pháp cho vấn đề của bạn là một trong hai khả năng:

  1. Xác định nhiều hơn một phương thức xác định từng chuyển đổi, hành vi hoặc bất cứ điều gì có thể. Đó là câu trả lời của Botz. Trong thế giới lập trình, điều này được gọi là Quá tải phương thức.

  2. Xác định một lớp cơ sở hoặc giao diện biết cách thực hiện tất cả những thứ bạn cần cho phương thức và có một phương thức chỉ lấy kiểu đó. Điều này có thể liên quan đến việc gói một stringExceptiontrong một lớp nhỏ để xác định cách bạn lên kế hoạch ánh xạ chúng đến việc thực hiện, nhưng sau đó mọi thứ đều rất rõ ràng và dễ đọc. Tôi có thể đến, bốn năm kể từ bây giờ và đọc mã của bạn và dễ dàng hiểu những gì đang xảy ra.

Việc bạn chọn tùy thuộc vào mức độ phức tạp của lựa chọn 1 và 2 và mức độ mở rộng của nó.

Vì vậy, đối với tình huống cụ thể của bạn, tôi sẽ tưởng tượng bạn chỉ cần rút ra một tin nhắn hoặc một cái gì đó từ ngoại lệ:

public interface IHasMessage
{
    string GetMessage();
}

public void test(string a, IHasMessage arg)
{
    //Use message
}

Bây giờ tất cả những gì bạn cần là các phương thức biến đổi a stringExceptionthành IHasMessage. Rất dễ.


xin lỗi @ Botz3000, chỉ cần chú ý tôi đã viết sai tên của bạn.
Chris Pfohl

Hoặc trình biên dịch có thể đe dọa loại là một loại kết hợp trong hàm và có giá trị trả về là cùng loại mà nó nhận được. TypeScript thực hiện điều này.
Alex

@Alex nhưng đó không phải là những gì C # làm.
Chris Pfohl

Đó là sự thật, nhưng nó có thể. Tôi đọc câu trả lời này vì nó sẽ không thể, tôi đã giải thích sai?
Alex

1
Điều này là các ràng buộc tham số chung và bản thân các khái quát chung là khá nguyên thủy so với các mẫu C ++. C # yêu cầu bạn báo trước cho trình biên dịch những thao tác nào được phép trên các loại chung. Cách để cung cấp thông tin đó là thêm một ràng buộc giao diện thực hiện (trong đó T: IDis Dùng). Nhưng bạn có thể không muốn loại của mình triển khai một số giao diện để sử dụng một phương thức chung hoặc bạn có thể muốn cho phép một số loại trong mã chung không có giao diện chung. Ví dụ. cho phép bất kỳ cấu trúc hoặc chuỗi nào để bạn có thể gọi Equals (v1, v2) để so sánh dựa trên giá trị.
Vakhtang

8

Nếu ChildClass có nghĩa là nó có nguồn gốc từ ParentClass, bạn có thể chỉ cần viết như sau để chấp nhận cả ParentClass và ChildClass;

public void test<T>(string a, T arg) where T: ParentClass 
{
    //do something
}

Mặt khác, nếu bạn muốn sử dụng hai loại khác nhau không có mối quan hệ thừa kế giữa chúng, bạn nên xem xét các loại thực hiện cùng một giao diện;

public interface ICommonInterface
{
    string SomeCommonProperty { get; set; }
}

public class AA : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;set;
    }
}

public class BB : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;
        set;
    }
}

sau đó bạn có thể viết hàm chung của bạn như;

public void Test<T>(string a, T arg) where T : ICommonInterface
{
    //do something
}

Ý kiến ​​hay, ngoại trừ tôi không nghĩ rằng tôi sẽ có thể làm điều này vì tôi muốn sử dụng chuỗi là một lớp niêm phong theo nhận xét ở trên ...
Mansfield

Đây là một vấn đề thiết kế trong thực tế. một hàm chung được sử dụng để thực hiện các hoạt động tương tự với việc sử dụng lại mã. cả hai nếu bạn đang có kế hoạch thực hiện các hoạt động khác nhau trong thân phương thức, thì các phương thức tách biệt là một cách tốt hơn (IMHO).
daryal

Thực tế, những gì tôi đang làm là viết một hàm ghi nhật ký lỗi đơn giản. Tôi muốn tham số cuối cùng đó là một chuỗi thông tin về lỗi hoặc ngoại lệ, trong trường hợp đó tôi lưu e.message + e.stacktrace dưới dạng một chuỗi.
Mansfield

bạn có thể viết một lớp mới, có isSuccesful, lưu tin nhắn và ngoại lệ làm thuộc tính. sau đó bạn có thể kiểm tra xem có đúng hay không và làm phần còn lại.
daryal

1

Vẫn như câu hỏi này, tôi vẫn nhận được những đánh giá ngẫu nhiên về lời giải thích của tôi ở trên. Lời giải thích vẫn hoàn toàn ổn, nhưng tôi sẽ trả lời lần thứ hai với một loại phục vụ tốt cho tôi thay thế cho các loại kết hợp (câu trả lời được đánh máy mạnh cho câu hỏi không được C # hỗ trợ trực tiếp ).

using System;
using System.Diagnostics;

namespace Union {
    [DebuggerDisplay("{currType}: {ToString()}")]
    public struct Either<TP, TA> {
        enum CurrType {
            Neither = 0,
            Primary,
            Alternate,
        }
        private readonly CurrType currType;
        private readonly TP primary;
        private readonly TA alternate;

        public bool IsNeither => currType == CurrType.Primary;
        public bool IsPrimary => currType == CurrType.Primary;
        public bool IsAlternate => currType == CurrType.Alternate;

        public static implicit operator Either<TP, TA>(TP val) => new Either<TP, TA>(val);

        public static implicit operator Either<TP, TA>(TA val) => new Either<TP, TA>(val);

        public static implicit operator TP(Either<TP, TA> @this) => @this.Primary;

        public static implicit operator TA(Either<TP, TA> @this) => @this.Alternate;

        public override string ToString() {
            string description = IsNeither ? "" :
                $": {(IsPrimary ? typeof(TP).Name : typeof(TA).Name)}";
            return $"{currType.ToString("")}{description}";
        }

        public Either(TP val) {
            currType = CurrType.Primary;
            primary = val;
            alternate = default(TA);
        }

        public Either(TA val) {
            currType = CurrType.Alternate;
            alternate = val;
            primary = default(TP);
        }

        public TP Primary {
            get {
                Validate(CurrType.Primary);
                return primary;
            }
        }

        public TA Alternate {
            get {
                Validate(CurrType.Alternate);
                return alternate;
            }
        }

        private void Validate(CurrType desiredType) {
            if (desiredType != currType) {
                throw new InvalidOperationException($"Attempting to get {desiredType} when {currType} is set");
            }
        }
    }
}

Lớp trên đại diện cho một loại có thể TP hoặc TA. Bạn có thể sử dụng nó như vậy (các loại tham khảo câu trả lời ban đầu của tôi):

// ...
public static Either<FishingBot, ConcreteMixer> DemoFunc(Either<JumpRope, PiCalculator> arg) {
  if (arg.IsPrimary) {
    return new FishingBot(arg.Primary);
  }
  return new ConcreteMixer(arg.Secondary);
}

// elsewhere:

var fishBotOrConcreteMixer = DemoFunc(new JumpRope());
var fishBotOrConcreteMixer = DemoFunc(new PiCalculator());

Ghi chú quan trọng:

  • Bạn sẽ gặp lỗi thời gian chạy nếu bạn không kiểm tra IsPrimary trước.
  • Bạn có thể kiểm tra bất kỳ IsNeither IsPrimaryhoặcIsAlternate .
  • Bạn có thể truy cập giá trị thông qua PrimaryAlternate
  • Có các bộ chuyển đổi ngầm định giữa TP / TA và Either để cho phép bạn chuyển các giá trị hoặc Eitherbất kỳ nơi nào mà người ta mong đợi. Nếu bạn làm vượt qua một Eithernơi mà một TAhoặc TPdự kiến, nhưng Eitherchứa các loại sai của giá trị mà bạn sẽ nhận được một lỗi thời gian chạy.

Tôi thường sử dụng điều này trong đó tôi muốn một phương thức trả về kết quả hoặc lỗi. Nó thực sự làm sạch mã phong cách đó. Tôi cũng rất thỉnh thoảng ( hiếm khi ) sử dụng điều này như một sự thay thế cho quá tải phương thức. Thực tế đây là một sự thay thế rất kém cho sự quá tải như vậy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.