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

Некоторые классы недоступны в качестве общих ограничений - Enum является другим.

Для делегатов самое близкое, что вы можете получить, это ": 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) включение подробного сообщения из-за странных условий отладки, связанных с инициализацией типа.
Сэм Харвелл
6
@MarcGravell: не генерирует исключение при нарушении статического инициализатора CA1065: Do not raise exceptions in unexpected locations... Я всегда предполагал, что вы должны использовать настраиваемое правило анализа кода, чтобы найти недопустимые использования вашего класса, которые обычно недоступны во время выполнения.
myermian
3
Начиная с C # 7.3 (выпущенного в мае 2018 г.), разрешено подобное ограничение where T : Delegate(и кто-то опубликовал новый ответ об этом ниже).
Йеппе Стиг Нильсен
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, вы можете использовать неуправляемое ограничение, чтобы указать, что параметр типа должен быть неуправляемым типом, не допускающим значения NULL. Неуправляемое ограничение позволяет вам писать многократно используемые процедуры для работы с типами, которыми можно манипулировать как блоки памяти.

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

Будущее C # от Microsoft Build 2018

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

mshwf
источник
Да, это возможно в C # 7.3 (с мая 2018 г.), и вы можете увидеть здесь примечания к выпуску .
Йеппе Стиг Нильсен
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" - это переменная типа, и компилятор не выполняет приведение 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: Я отредактировал ваш ответ, чтобы поставить свой комментарий в конце, поскольку в нем есть блок кода.
Сэм Харвелл
2

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

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

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

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

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

maxspan
источник
1

Согласно MSDN

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

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

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Рахул Никате
источник
Почему вы здесь повторяете вопрос? Вы не говорите нам ничего нового.
Elmue