C # Generics sẽ không cho phép các Ràng buộc Loại Đại diện


79

Có thể định nghĩa một lớp trong C # như vậy không

class GenericCollection<T> : SomeBaseCollection<T> where T : Delegate

Tôi không thể thực hiện được điều này đêm qua trong .NET 3.5. Tôi đã thử sử dụng

delegate, Delegate, Action<T> and Func<T, T>

Đối với tôi, dường như điều này nên được cho phép theo một cách nào đó. Tôi đang cố gắng triển khai EventQueue của riêng mình.

Tôi đã kết thúc chỉ làm điều này [suy nghĩ gần đúng ban đầu bạn].

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

Nhưng sau đó tôi mất khả năng sử dụng lại cùng một định nghĩa cho các loại hàm khác nhau.

Suy nghĩ?

Câu trả lời:


66

Một số lớp không có sẵn dưới dạng đối chiếu chung - Enum là một lớp khác.

Đối với đại biểu, gần nhất bạn có thể nhận được là ": class", có thể sử dụng phản xạ để kiểm tra (ví dụ: trong phương thức khởi tạo tĩnh) rằng T có phải là đại biểu hay không:

static GenericCollection()
{
    if (!typeof(T).IsSubclassOf(typeof(Delegate)))
    {
        throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
    }
}

8
+1 cho: 1) sử dụng hàm tạo tĩnh và 2) bao gồm một thông báo chi tiết do các điều kiện gỡ lỗi kỳ lạ xung quanh việc khởi tạo kiểu.
Sam Harwell

6
@MarcGravell: Không vi phạm ngoại lệ trong trình khởi tạo tĩnh CA1065: Do not raise exceptions in unexpected locations... Tôi luôn cho rằng bạn nên sử dụng quy tắc phân tích mã tùy chỉnh để tìm các cách sử dụng không hợp lệ của lớp bạn thường không có sẵn tại thời điểm chạy.
myermian

3
Bắt đầu từ C # 7.3 (phát hành tháng 5 năm 2018), nó được phép hạn chế như thế này where T : Delegate, (và ai đó đã đăng câu trả lời mới về điều đó bên dưới).
Jeppe Stig Nielsen

16

Có nó có thể trong C # 7.3, ràng buộc gia đình tăng lên bao gồm Enum, Delegateunmanagedcác loại. Bạn có thể viết mã này mà không gặp sự cố:

void M<D, E, T>(D d, E e, T* t) where D : Delegate where E : Enum where T : unmanaged
    {

    }

Từ Tài liệu :

Bắt đầu với C # 7.3, bạn có thể sử dụng ràng buộc không được quản lý để chỉ định rằng tham số kiểu phải là kiểu không được quản lý không thể null. Ràng buộc không được quản lý cho phép bạn viết các quy trình có thể tái sử dụng để làm việc với các loại có thể được thao tác dưới dạng khối bộ nhớ

Liên kết hữu ích:

Tương lai của C # , từ Microsoft Build 2018

Có gì mới trong C # 7.3?


Có, có thể có trong C # 7.3 (kể từ tháng 5 năm 2018) và bạn có thể xem ghi chú phát hành tại đây .
Jeppe Stig Nielsen

13

Chỉnh sửa: Một số cách giải quyết được đề xuất được đề xuất trong các bài viết này:

http://jacobcarpenters.blogspot.com/2006/06/c-30-and-delegate-conversion.html

http://jacobcarpenters.blogspot.com/2006_11_01_archive.html


Từ đặc tả C # 2.0, chúng ta có thể đọc (20.7, Ràng buộc):

Ràng buộc kiểu lớp phải đáp ứng các quy tắc sau:

  • Kiểu phải là một kiểu lớp.
  • Loại không được niêm phong.
  • Loại không được là một trong các loại sau: System.Array, System.Delegate, System.Enum hoặc System.ValueType .
  • Loại không được là đối tượng. Bởi vì tất cả các kiểu bắt nguồn từ đối tượng, một ràng buộc như vậy sẽ không có hiệu lực nếu nó được cho phép.
  • Nhiều nhất một ràng buộc cho một tham số kiểu đã cho có thể là một kiểu lớp.

Và chắc chắn VS2008 sẽ phát ra lỗi:

error CS0702: Constraint cannot be special class 'System.Delegate'

Để biết thông tin và điều tra về vấn đề này, hãy đọc ở đây .


10

Nếu bạn sẵn sàng phụ thuộc vào thời gian biên dịch vào IL Weaver, bạn có thể làm điều này với Fody .

Sử dụng addin này vào Fody https://github.com/Fody/ExtraConstraints

Mã của bạn có thể trông như thế này

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
} 

Và được biên dịch thành

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}

Liên kết bị hỏng. Bạn có một cái hiện tại không?
Justin Morgan

3

Delegate đã hỗ trợ chuỗi. Điều này không đáp ứng nhu cầu của bạn?

public class EventQueueTests
{
    public void Test1()
    {
        Action myAction = () => Console.WriteLine("foo");
        myAction += () => Console.WriteLine("bar");

        myAction();
        //foo
        //bar
    }

    public void Test2()
    {
        Action<int> myAction = x => Console.WriteLine("foo {0}", x);
        myAction += x => Console.WriteLine("bar {0}", x);
        myAction(3);
        //foo 3
        //bar 3
    }

    public void Test3()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        myFunc += x => { Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 3
        //4
    }

    public void Test4()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        Func<int, int> myNextFunc = x => { x = myFunc(x);  Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myNextFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 5
        //6
    }

}

thats không thực sự là chức năng tôi đang tìm kiếm ... Tôi đã cố gắng để tạo ra một loại hạn chế trên lớp generic của tôi ...
Nicholas Mancuso

3

Tôi đã gặp một tình huống mà tôi cần phải giải quyết Delegatenội bộ nhưng tôi muốn có một hạn chế chung. Cụ thể, tôi muốn thêm một trình xử lý sự kiện bằng cách sử dụng phản chiếu, nhưng tôi muốn sử dụng một đối số chung cho đại biểu. Đoạn mã dưới đây KHÔNG hoạt động, vì "Trình xử lý" là một biến kiểu và trình biên dịch sẽ không truyền Handlertới Delegate:

public void AddHandler<Handler>(Control c, string eventName, Handler d) {
  c.GetType().GetEvent(eventName).AddEventHandler(c, (Delegate) d);
}

Tuy nhiên, bạn có thể chuyển một hàm thực hiện chuyển đổi cho bạn. convertnhận Handlerđối số và trả về Delegate:

public void AddHandler<Handler>(Control c, string eventName, 
                  Func<Delegate, Handler> convert, Handler d) {
      c.GetType().GetEvent(eventName).AddEventHandler(c, convert(d));
}

Bây giờ trình biên dịch là hạnh phúc. Gọi phương thức là dễ dàng. Ví dụ: đính kèm vào KeyPresssự kiện trên điều khiển Windows Forms:

AddHandler<KeyEventHandler>(someControl, 
           "KeyPress", 
           (h) => (KeyEventHandler) h,
           SomeControl_KeyPress);

nơi SomeControl_KeyPresslà mục tiêu của sự kiện. Chìa khóa là lambda bộ chuyển đổi - nó không hoạt động, nhưng nó thuyết phục trình biên dịch mà bạn đã cấp cho nó một đại biểu hợp lệ.

(Bắt đầu 280Z28) @Justin: Tại sao không sử dụng cái này?

public void AddHandler<Handler>(Control c, string eventName, Handler d) { 
  c.GetType().GetEvent(eventName).AddEventHandler(c, d as Delegate); 
} 

(Cuối 280Z28)


1
@Justin: Tôi đã chỉnh sửa câu trả lời của bạn để đưa nhận xét của tôi vào cuối vì nó có một khối mã.
Sam Harwell

2

Như đã đề cập ở trên, bạn không thể có Delegates và Enum như một ràng buộc chung. System.ObjectSystem.ValueTypecũng không thể được sử dụng như một ràng buộc chung.

Công việc xung quanh có thể là nếu bạn xây dựng một cuộc gọi thích hợp trong IL của bạn. Nó sẽ hoạt động tốt.

Đây là một ví dụ điển hình của Jon Skeet.

http://code.google.com/p/unconstrained-melody/

Tôi đã lấy tài liệu tham khảo của mình từ cuốn sách C # in Depth của Jon Skeet , ấn bản thứ 3.


1

Theo MSDN

Lỗi biên dịch CS0702

Ràng buộc không được là 'định danh' lớp đặc biệt Các loại sau không được dùng làm ràng buộc:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.

Tại sao bạn lặp lại câu hỏi ở đây? Bạn không cho chúng tôi biết bất cứ điều gì mới.
Elmue
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.