Cách thực hiện chuyên môn hóa mẫu trong C #


84

Bạn sẽ làm chuyên môn hóa C # như thế nào?

Tôi sẽ đặt ra một vấn đề. Bạn có một loại mẫu, bạn không biết nó là gì. Nhưng bạn có biết nếu nó bắt nguồn từ XYZbạn muốn gọi .alternativeFunc(). Một cách tuyệt vời là gọi một hàm hoặc lớp chuyên biệt và có normalCalltrả về .normalFunc()trong khi có chuyên môn hóa khác trên bất kỳ kiểu XYZgọi xuất phát nào .alternativeFunc(). Làm thế nào điều này sẽ được thực hiện trong C #?

Câu trả lời:


86

Trong C #, cách gần nhất với chuyên môn hóa là sử dụng quá tải cụ thể hơn; tuy nhiên, điều này là giòn và không bao gồm mọi cách sử dụng có thể. Ví dụ:

void Foo<T>(T value) {Console.WriteLine("General method");}
void Foo(Bar value) {Console.WriteLine("Specialized method");}

Ở đây, nếu trình biên dịch biết các loại khi biên dịch, nó sẽ chọn loại cụ thể nhất:

Bar bar = new Bar();
Foo(bar); // uses the specialized method

Tuy nhiên....

void Test<TSomething>(TSomething value) {
    Foo(value);
}

sẽ sử dụng Foo<T>ngay cả cho TSomething=Bar, vì nó được ghi vào lúc biên dịch.

Một cách tiếp cận khác là sử dụng kiểm tra kiểu trong một phương pháp chung - tuy nhiên, đây thường là một ý tưởng tồi và không được khuyến khích.

Về cơ bản, C # chỉ không muốn bạn làm việc với các chuyên ngành, ngoại trừ tính đa hình:

class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}

Ở đây Bar.Foosẽ luôn giải quyết ghi đè chính xác.


63

Giả sử bạn đang nói về chuyên môn hóa mẫu vì nó có thể được thực hiện với các mẫu C ++ - một tính năng như thế này không thực sự khả dụng trong C #. Điều này là do các generic C # không được xử lý trong quá trình biên dịch và là một tính năng của thời gian chạy.

Tuy nhiên, bạn có thể đạt được hiệu quả tương tự bằng cách sử dụng các phương pháp mở rộng C # 3.0. Đây là một ví dụ cho thấy cách thêm phương thức mở rộng chỉ cho MyClass<int>loại, giống như chuyên môn hóa mẫu. Tuy nhiên, lưu ý rằng bạn không thể sử dụng điều này để ẩn triển khai mặc định của phương pháp, vì trình biên dịch C # luôn ưu tiên các phương thức chuẩn hơn các phương thức mở rộng:

class MyClass<T> {
  public int Foo { get { return 10; } }
}
static class MyClassSpecialization {
  public static int Bar(this MyClass<int> cls) {
    return cls.Foo + 20;
  }
}

Bây giờ bạn có thể viết cái này:

var cls = new MyClass<int>();
cls.Bar();

Nếu bạn muốn có một trường hợp mặc định cho phương thức sẽ được sử dụng khi không có chuyên môn hóa nào được cung cấp, thì tôi tin rằng việc viết một Barphương thức mở rộng chung sẽ thực hiện thủ thuật:

  public static int Bar<T>(this MyClass<T> cls) {
    return cls.Foo + 42;
  }

Foo property vs Bar method ... không thực sự có vẻ như là một chuyên môn hóa điển hình ...
Marc Gravell

2
Không, nó không phải specilaziation điển hình, nhưng đó là điều duy nhất dễ dàng mà bạn có thể làm ... (AFAIK)
Tomas Petricek

3
Điều này có vẻ cũng hoạt động tốt mà không cần sử dụng staticcác phương thức mở rộng — chỉ là các phương thức có kiểu chung. Đó là, vấn đề được chỉ ra trong câu trả lời @MarcGravell có vẻ bị phá vỡ bằng cách “tạo mẫu” phương pháp dựa trên một đối số như MyClass<T>/ MyClass<int>, thay vì tạo mẫu phương thức thành kiểu “dữ liệu” cụ thể ( T/ int).
Slipp D. Thompson

4
Hạn chế bổ sung là nó sẽ không hoạt động đối với các cuộc gọi chung chung gián tiếp, ví dụ như từ bên trong một phương thức void CallAppropriateBar<T>() { (new MyClass<T>()).Bar(); }.
BartoszKP

18

Bằng cách thêm một lớp trung gian và một từ điển, có thể chuyên môn hóa .

Để chuyên về T, chúng ta tạo một giao diện chung, có một phương thức được gọi là (ví dụ) Áp dụng. Đối với các lớp cụ thể mà giao diện được triển khai, xác định phương pháp Áp dụng cụ thể cho lớp đó. Lớp trung gian này được gọi là lớp tính trạng.

Lớp đặc điểm đó có thể được chỉ định như một tham số trong lệnh gọi của phương thức chung, sau đó (tất nhiên) luôn thực hiện đúng.

Thay vì chỉ định nó theo cách thủ công, lớp đặc điểm cũng có thể được lưu trữ trong toàn cục IDictionary<System.Type, object>. Sau đó nó có thể được tra cứu và thì đấy, bạn có chuyên môn thực sự ở đó.

Nếu thuận tiện, bạn có thể hiển thị nó trong một phương pháp mở rộng.

class MyClass<T>
{
    public string Foo() { return "MyClass"; }
}

interface BaseTraits<T>
{
    string Apply(T cls);
}

class IntTraits : BaseTraits<MyClass<int>>
{
    public string Apply(MyClass<int> cls)
    {
        return cls.Foo() + " i";
    }
}

class DoubleTraits : BaseTraits<MyClass<double>>
{
    public string Apply(MyClass<double> cls)
    {
        return cls.Foo() + " d";
    }
}

// Somewhere in a (static) class:
public static IDictionary<Type, object> register;
register = new Dictionary<Type, object>();
register[typeof(MyClass<int>)] = new IntTraits();
register[typeof(MyClass<double>)] = new DoubleTraits();

public static string Bar<T>(this T obj)
{
    BaseTraits<T> traits = register[typeof(T)] as BaseTraits<T>;
    return traits.Apply(obj);
}

var cls1 = new MyClass<int>();
var cls2 = new MyClass<double>();

string id = cls1.Bar();
string dd = cls2.Bar();

Xem liên kết này đến blog gần đây của tôi và các phần tiếp theo để biết mô tả và mẫu mở rộng.


Đây là mẫu máy và nó là một cách đàng hoàng để đối phó với một số những thiếu sót của Generics
Yaur

1
@Yaur Tôi trông giống như một mẫu Trang trí sách giáo khoa đối với tôi.
Slipp D. Thompson

13

Tôi cũng đang tìm kiếm một mẫu để mô phỏng chuyên môn hóa mẫu. Có một số cách tiếp cận có thể hiệu quả trong một số trường hợp. Tuy nhiên những gì về trường hợp

static void Add<T>(T value1, T value2)
{
    //add the 2 numeric values
}

Có thể chọn hành động bằng cách sử dụng các câu lệnh, ví dụ if (typeof(T) == typeof(int)). Nhưng có một cách tốt hơn để mô phỏng chuyên môn hóa mẫu thực với chi phí của một lệnh gọi hàm ảo duy nhất:

public interface IMath<T>
{
    T Add(T value1, T value2);
}

public class Math<T> : IMath<T>
{
    public static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>();

    //default implementation
    T IMath<T>.Add(T value1, T value2)
    {
        throw new NotSupportedException();    
    }
}

class Math : IMath<int>, IMath<double>
{
    public static Math P = new Math();

    //specialized for int
    int IMath<int>.Add(int value1, int value2)
    {
        return value1 + value2;
    }

    //specialized for double
    double IMath<double>.Add(double value1, double value2)
    {
        return value1 + value2;
    }
}

Bây giờ chúng ta có thể viết mà không cần phải biết trước loại:

static T Add<T>(T value1, T value2)
{
    return Math<T>.P.Add(value1, value2);
}

private static void Main(string[] args)
{
    var result1 = Add(1, 2);
    var result2 = Add(1.5, 2.5);

    return;
}

Nếu chuyên môn hóa không chỉ được gọi cho các kiểu được triển khai mà còn cả các kiểu dẫn xuất, người ta có thể sử dụng một Intham số cho giao diện. Tuy nhiên, trong trường hợp này, kiểu trả về của các phương thức không thể thuộc kiểu chung Tnữa.


Điều này là khá tuyệt vời, cảm ơn bạn. Nó cho phép tôi tạo một giao diện chung để gọi vào một loạt các phương thức đã có từ trước, mỗi phương thức được viết cho các kiểu cụ thể, mà không thể (hoặc ít nhất là rất khó) không thể viết lại một cách chung chung. Nó đã bắt đầu trông giống như tôi sẽ phải làm một số khủng khiếp if (type == typeof(int))trở lại và sau đó biến đổi thành kiểu generic w / đấm bốc thêm / unboxing return (T)(object)result;(vì loại này chỉ hợp lý nổi tiếng, không tĩnh biết)
Andrew Wright

6

Một số câu trả lời được đề xuất đang sử dụng thông tin loại thời gian chạy: vốn dĩ chậm hơn các cuộc gọi phương thức ràng buộc thời gian biên dịch.

Trình biên dịch không thực thi chuyên môn hóa như trong C ++.

Tôi khuyên bạn nên xem PostSharp để biết cách chèn mã sau khi trình biên dịch thông thường được hoàn thành để đạt được hiệu ứng tương tự như C ++.


4

Tôi nghĩ rằng có một cách để đạt được điều đó với .NET 4+ bằng cách sử dụng độ phân giải động:

static class Converter<T>
{
    public static string Convert(T data)
    {
        return Convert((dynamic)data);
    }

    private static string Convert(Int16 data) => $"Int16 {data}";
    private static string Convert(UInt16 data) => $"UInt16 {data}";
    private static string Convert(Int32 data) => $"Int32 {data}";
    private static string Convert(UInt32 data) => $"UInt32 {data}";
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Converter<Int16>.Convert(-1));
        Console.WriteLine(Converter<UInt16>.Convert(1));
        Console.WriteLine(Converter<Int32>.Convert(-1));
        Console.WriteLine(Converter<UInt32>.Convert(1));
    }
}

Đầu ra:

Int16 -1
UInt16 1
Int32 -1
UInt32 1

Điều này cho thấy rằng một triển khai khác được gọi cho các loại khác nhau.


2
Tôi muốn khóc một chút.
user2864740

0

Nếu bạn chỉ muốn kiểm tra xem một loại có được lấy từ XYZ hay không, thì bạn có thể sử dụng:

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));

Nếu vậy, bạn có thể truyền "theunknownobject" tới XYZ và gọi thay thế alternativeFunc () như sau:

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc();

Hi vọng điêu nay co ich.


1
Tôi không biết nhiều về C #, nhưng bất kỳ ai đã bỏ phiếu cho bạn nên nói tại sao. Tôi không biết có gì sai khi câu trả lời của bạn hoặc nếu có gì sai với nó.

Cũng không chắc nữa. Nó có vẻ đủ hợp lệ với tôi. Mặc dù hơi dài dòng hơn mức cần thiết.
jalf

3
Không phải tôi mà là vì câu trả lời hoàn toàn không liên quan đến câu hỏi. Tra cứu"c++ template specialization"
georgiosd

Điều này không phải lúc nào cũng hiệu quả. Ví dụ, bạn không thể thấy liệu T có phải là bool hay không và sau đó chuyển thành bool.
Kos
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.