C # Generics не дозволить обмеження типу делегатів


79

Чи можна визначити клас в C # таким, що

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

Я не міг за все своє життя здійснити цього минулого вечора в .NET 3.5. Я спробував використовувати

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

Мені здається, що це має бути якимось чином допустимим. Я намагаюся реалізувати власну EventQueue.

У підсумку я просто зробив це [примітивне наближення, пам’ятайте].

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

Але тоді я втрачаю можливість повторного використання одного і того ж визначення для різних типів функцій.

Думки?

Відповіді:


66

Ряд класів недоступні як загальні протилежності - Енум - інший.

Для делегатів найближчим, що ви можете отримати, є ": class", можливо, використовуючи відображення для перевірки (наприклад, у статичному конструкторі), що T є делегатом:

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

8
+1 для: 1) використання статичного конструктора та 2) включення детального повідомлення через дивні умови налагодження, що оточують ініціалізацію типу.
Sam Harwell

6
@MarcGravell: Чи не порушує виняток у статичному ініціалізаторі CA1065: Do not raise exceptions in unexpected locations... Я завжди припускав, що ви повинні використовувати спеціальне правило аналізу коду для пошуку недійсних звичок вашого класу, які зазвичай не доступні під час виконання.
myermian

3
Починаючи з C # 7.3 (випущений у травні 2018 року), дозволяється обмежуватись таким чином where T : Delegate, (і хтось опублікував нову відповідь про це нижче).
Jeppe Stig Nielsen,

16

Так , це можливо в C # 7.3, Обмеження сім'ї доповнилося Enum, Delegateі unmanagedтипами. Ви можете написати цей код без проблем:

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

    }

З Документів :

Починаючи з C # 7.3, ви можете використовувати некероване обмеження, щоб вказати, що параметр типу повинен бути ненульованим некерованим типом. Некероване обмеження дозволяє писати багаторазові підпрограми для роботи з типами, якими можна керувати як блоками пам'яті

Корисні посилання:

Майбутнє C # від Microsoft Build 2018

Що нового в C # 7.3?


Так, це можливо в C # 7.3 (з травня 2018 року), і ви можете побачити примітки до випуску тут .
Jeppe Stig Nielsen,

13

Редагувати: Деякі запропоновані методи роботи пропонуються в цих статтях:

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

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


З специфікації C # 2.0 ми можемо прочитати (20.7, Обмеження):

Обмеження типу класу повинні відповідати наступним правилам:

  • Тип повинен бути типом класу.
  • Тип не повинен бути герметичним.
  • Тип не повинен бути одним із таких типів: System.Array, System.Delegate, System.Enum або System.ValueType .
  • Тип не повинен бути об'єктом. Оскільки всі типи походять від об'єкта, таке обмеження не мало б ефекту, якби це було дозволено.
  • Максимум одне обмеження для даного параметра типу може бути типом класу.

І впевнений, VS2008 видає помилку:

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

Інформацію та розслідування з цього питання читайте тут .


10

Якщо ви бажаєте взяти залежність часу компіляції від IL Weaver, ви можете зробити це за допомогою Fody .

Використовуючи цей додаток до Fody https://github.com/Fody/ExtraConstraints

Ваш код може виглядати так

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

І будьте до цього складені

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

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

Недіюче посилання. У вас є поточний?
Джастін Морган,

3

Делегат вже підтримує ланцюжок. Чи це не відповідає вашим потребам?

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
    }

}

це насправді не функціональність, яку я шукаю ... Я намагався обмежити тип для мого загального класу ...
Ніколас Манкузо,

3

Я зіткнувся з ситуацією, коли мені потрібно було мати справу з Delegateвнутрішньою ситуацією, але я хотів загальне обмеження. Зокрема, я хотів додати обробник події за допомогою відображення, але я хотів використати загальний аргумент для делегата. Наведений нижче код не працює, так як «Оброблювач» є змінною типу, і компілятор буде кинутий Handlerна Delegate:

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

Однак ви можете передати функцію, яка виконує перетворення за вас. convertприймає Handlerаргумент і повертає Delegate:

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

Тепер компілятор задоволений. Викликати метод легко. Наприклад, приєднання до KeyPressподії на елементі керування Windows Forms:

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

де SomeControl_KeyPressціль події. Ключ - перетворювач лямбда - він не працює, але переконує компілятор, що ви надали йому дійсний делегат.

(Початок 280Z28) @Justin: Чому б не використовувати це?

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

(Кінець 280Z28)


1
@Justin: Я відредагував вашу відповідь, щоб додати мій коментар до кінця, оскільки в ньому є блок коду.
Sam Harwell

2

Як згадувалося вище, ви не можете мати делегатів та Enum як загальне обмеження. System.Objectа System.ValueTypeтакож не може використовуватися як загальне обмеження.

Це можна зробити, якщо ви побудуєте відповідний виклик у своєму ІЛ. Це буде працювати нормально.

Ось хороший приклад Джона Скіта.

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

Я взяв свої посилання з книги Джона Скіта C # у Depth , 3-е видання.


1

За даними MSDN

Помилка компілятора CS0702

Обмеження не може бути спеціальним ідентифікатором класу. Наступні типи не можуть використовуватися як обмеження:

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

Чому ти тут повторюєш запитання? Ви не говорите нам нічого нового.
Елму
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.