С# Generics не допускает ограничений типа делегата
Можно ли определить класс в С# так, чтобы
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
Несколько классов недоступны в качестве общих противопоказаний - Enum является другим.
Для делегатов наиболее близким вам может быть ": class", возможно, используя рефлексию для проверки (например, в статическом конструкторе), что T является делегатом:
static GenericCollection()
{
if (!typeof(T).IsSubclassOf(typeof(Delegate)))
{
throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
}
}
Ответ 2
Изменить: Некоторые предлагаемые варианты работы предлагаются в следующих статьях:
http://jacobcarpenters.blogspot.com/2006/06/c-30-and-delegate-conversion.html
http://jacobcarpenters.blogspot.com/2006_11_01_archive.html
Из спецификация С# 2.0 мы можем прочитать (20.7, Constraints):
Ограничение типа класса должно удовлетворять следующим правилам:
- Тип должен быть типом класса.
- Тип не должен быть закрыт.
- Тип не должен быть одним из следующих типов: System.Array, System.Delegate, System.Enum или System.ValueType.
- Тип не должен быть объектом. Поскольку все типы происходят от объекта, такое ограничение не будет иметь никакого эффекта, если бы оно было разрешено.
- Не более одного ограничения для данного параметра типа может быть типом класса.
И, конечно же, VS2008 выплевывает ошибку:
error CS0702: Constraint cannot be special class 'System.Delegate'
Для информации и исследований по этой проблеме читайте здесь.
Ответ 3
Если вы готовы взять зависимость времени компиляции от IL Weaver, вы можете сделать это с помощью Fody.
Использование этого addin для 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
{
}
}
Ответ 4
Делегат уже поддерживает цепочку. Разве это не отвечает вашим потребностям?
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
}
}
Ответ 5
Я столкнулся с ситуацией, когда мне нужно было иметь дело с 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
- цель события. Ключом является конвертер lambda - он не работает, но он убеждает компилятор, что вы дали ему действительный делегат.
(Начало 280Z28) @Justin: Почему бы не использовать это?
public void AddHandler<Handler>(Control c, string eventName, Handler d) {
c.GetType().GetEvent(eventName).AddEventHandler(c, d as Delegate);
}
(конец 280Z28)
Ответ 6
Как уже упоминалось выше, у вас не может быть делегирование и Enum в качестве общего ограничения. System.Object
и System.ValueType
также не могут использоваться в качестве общего ограничения.
Работа вокруг может быть, если вы построите соответствующий вызов в IL. Он будет работать нормально.
Вот хороший пример: Джон Скит.
http://code.google.com/p/unconstrained-melody/
Я взял мои рекомендации из книги Джона Скита С# в издании Dept 3.
Ответ 7
Согласно MSDN
Ошибка компилятора CS0702
Ограничение не может быть специальным идентификатором класса. Следующие типы не могут использоваться в качестве ограничений:
- System.Object
- System.Array
- System.Delegate
- System.Enum
- System.ValueType.