Универсальные языки C# не допускают ограничений типа делегата

#c# #generics #events #delegates #constraints

Вопрос:

Можно ли определить класс в 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;
}
 

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

Мысли?

Ответ №1:

Ряд классов недоступны в качестве общих противопоказаний — Перечисление является другим.

Для делегатов самое близкое, что вы можете получить, это «: класс», возможно, используя отражение, чтобы проверить (например, в статическом конструкторе), что T является делегатом:

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

Комментарии:

1. 1 для: 1) использования статического конструктора и 2) включения подробного сообщения из-за странных условий отладки, связанных с инициализацией типа.

2. @MarcGravell: Не нарушает исключение в статическом инициализаторе CA1065: Do not raise exceptions in unexpected locations … Я всегда исходил из предположения, что вы должны использовать правило анализа пользовательского кода, чтобы найти недопустимые способы использования вашего класса, которые обычно недоступны во время выполнения.

3. Начиная с C# 7.3 (выпущен в мае 2018 года), разрешается ограничивать вот так where T : Delegate , (и кто-то опубликовал новый ответ об этом ниже).

Ответ №2:

Да, это возможно в 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, вы можете использовать ограничение неуправляемого, чтобы указать, что параметр типа должен быть неуправляемым типом, не допускающим значения null. Неуправляемое ограничение позволяет писать многоразовые процедуры для работы с типами, которыми можно управлять как блоками памяти

Полезные ссылки:

Будущее C#, начиная с Microsoft Build 2018

Что нового в C# 7.3?

Комментарии:

1. Да, это возможно в C# 7.3 (с мая 2018 года), и вы можете ознакомиться с примечаниями к выпуску здесь .

2. Это должен быть новый принятый ответ, текущий-с 2008 года.. Сейчас это очень устарело.

Ответ №3:

Изменить: Некоторые предлагаемые обходные пути предлагаются в этих статьях:

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.Enum или System.ValueType.
  • Тип не должен быть объектом. Поскольку все типы являются производными от объекта, такое ограничение не имело бы никакого эффекта, если бы оно было разрешено.
  • Не более одного ограничения для данного параметра типа может быть типом класса.

И, конечно же, VS2008 выдает ошибку:

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

Для получения информации и расследования по этому вопросу читайте здесь.

Ответ №4:

Если вы готовы принять зависимость во время компиляции от IL-ткача, вы можете сделать это с помощью 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
    {
    }
}
 

Комментарии:

1. Неработающая ссылка. У вас есть действующий?

Ответ №5:

Делегат уже поддерживает цепочку. Разве это не соответствует вашим потребностям?

 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
    }

}
 

Комментарии:

1. на самом деле это не та функциональность, которую я ищу… Я пытался создать ограничение типа для своего универсального класса…

Ответ №6:

Я столкнулся с ситуацией, когда мне нужно было разобраться с 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: Я отредактировал ваш ответ, чтобы поместить свой комментарий в конце, так как в нем есть блок кода.

Ответ №7:

Как упоминалось выше, у вас не может быть делегатов и перечисления в качестве общего ограничения. System.Object а System.ValueType также не может использоваться в качестве общего ограничения.

Обойти это можно, если вы создадите соответствующий вызов в своем IL. Это будет работать нормально.

Вот хороший пример Джона Скита.

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

Я взял свои ссылки из книги Джона Скита «C# в глубине«, 3-е издание.

Ответ №8:

Согласно MSDN

Ошибка компилятора CS0702

Ограничение не может быть специальным идентификатором класса. Следующие типы не могут использоваться в качестве ограничений:

  • Система.Объект
  • Система.Массив
  • Система.Делегат
  • Система.Перечисление
  • Система.Тип значения.

Комментарии:

1. Почему вы повторяете этот вопрос здесь? Ты не сообщаешь нам ничего нового.