2012-03-31 2 views
5

Я пытался создать общее событие. В основном это должно выглядеть следующим образом:EventHandlers and Covariance

namespace DelegateTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var lol = new SomeClass(); 
      lol.SomeEvent += handler; 
     } 

     static void handler(object sender, SomeDerivedClass e) 
     { 

     } 

    } 

    class SomeClass 
    { 

     public delegate void SomeEventDelegate<in T>(object sender, T data); 
     public event SomeEventDelegate<ISomeInterface> SomeEvent; 

    } 

    interface ISomeInterface 
    { 
    } 

    class SomeDerivedClass : ISomeInterface 
    { 
    } 
} 

Я хочу, чтобы позволить пользователю передавать любой делегат, который второй параметр является производным от «ISomeInterface.»

"in" указывает на противоречие, не так ли? Это означает, что если API ожидает что-то более общее, вы можете передать ему что-то более конкретное (в моей базе «ISomeInterface» будет общий, и мой «SomeDerivedClass» будет конкретным.) Мне, однако, сообщают мой компилятор что «никакая перегрузка для обработчика методов не соответствует DelegateTest.SomeClass.SomeEventDelegate».

Мне интересно, почему это не работает. Какие проблемы могут быть вызваны, если это так? Или мне что-то не хватает, чтобы он работал?

Заранее спасибо!

+0

См. Http://stackoverflow.com/questions/129453/net-eventhandlers-generic-or-no –

+0

Я думаю, что у вас не может быть одного события для обработки нескольких типов, у вас могут быть только делегаты. Что вы можете сделать, так это изменить обработчик, чтобы вместо этого принять «ISomeInterface», а затем проверить тип отправленного объекта. Или используйте сам интерфейс. http://msdn.microsoft.com/en-us/library/dd469484.aspx – BrunoLM

ответ

6

"in" указывает на противоречие, не так ли?

Да.

Это означает, что если API ожидает что-то более общее, вы можете передать что-то более конкретно (в моей базе «ISomeInterface» было бы вообще, и мой «SomeDerivedClass» будет конкретным).

№ Делегат-контравариантность позволяет делегату ссылаться на метод с типами параметров, которые менее производны, чем в типе делегата. Например, предположим, что ISomeInterface имел базовый интерфейс:

interface ISomeBaseInterface 
{ 
} 

interface ISomeInterface : ISomeBaseInterface 
{ 
} 

И пусть handler взял ISomeBaseInterface вместо SomeDerivedClass:

static void handler(object sender, ISomeBaseInterface e) 

new SomeClass().SomeEvent += handler Тогда будет работать.

Вот почему исходный код не типобезопасен: Когда SomeClass поднимает SomeEvent, он потенциально может пройти ничего, который реализует ISomeInterface в качестве data аргумента. Например, он может передать экземпляр SomeDerivedClass, но он также может передать экземпляр

class SomeOtherDerivedClass : ISomeInterface 
{ 
} 

Если вы смогли зарегистрировать void handler(object sender, SomeDerivedClass e) с событием, то этот обработчик будет заводиться быть вызван с SomeOtherDerivedClass, который Безразлично» т работы.

Таким образом, вы можете зарегистрировать обработчик событий, которые более общий чем тип события, а не обработчики событий, которые более конкретных.

UPDATE: Вы прокомментировали:

Ну, я на самом деле хочу итерацию по списку и проверить типы.Поэтому, если событие должно было быть запущено с объектом данных типа, например SomeOtherDerivedObject, программа перебирает список методов, подписанных на событие, пока не найдет тот, который соответствует сигнатуре (object, SomeOtherDerivedObject). Таким образом, само событие будет использоваться только для хранения, а не для фактического вызова делегатов.

Я не думаю, что C# позволяет объявить event, который работает с произвольными типами делегатов. Вот как вы можете написать методы, которые добавляют обработчик событий и вызывать их:

class SomeClass 
{ 
    private Delegate handlers; 

    public delegate void SomeEventDelegate<in T>(object sender, T data); 

    public void AddSomeEventHandler<T>(SomeEventDelegate<T> handler) 
    { 
     this.handlers = Delegate.Combine(this.handlers, handler); 
    } 

    protected void OnSomeEvent<T>(T data) 
    { 
     if (this.handlers != null) 
     { 
      foreach (SomeEventDelegate<T> handler in 
       this.handlers.GetInvocationList().OfType<SomeEventDelegate<T>>()) 
      { 
       handler(this, data); 
      } 
     } 
    } 
} 
+0

Спасибо. Есть ли способ заставить его работать наоборот? В настоящее время я использую Java-подобный подход (интерфейсы + addListener.) – haiyyu

+1

Если вы абсолютно уверены, что 'SomeClass' с только когда-либо передающими экземплярами' SomeDerivedClass' как аргумент 'data', то вы можете добавить явное выражение : 'lol.SomeEvent + = (отправитель, e) => обработчик (отправитель, (SomeDerivedClass) e)'. Но тогда вы можете также объявить 'SomeEvent' как' SomeEventDelegate '. –

+0

Ну, я действительно хочу перебирать список и проверять типы. Поэтому, если событие должно было быть запущено с объектом данных типа, например SomeOtherDerivedObject, программа перебирает список методов, подписанных на событие, пока не найдет тот, который соответствует сигнатуре (object, SomeOtherDerivedObject). Таким образом, само событие будет использоваться только для хранения, а не для фактического вызова делегатов. Надеюсь, это было понятно. – haiyyu

2

Одним из основного раздражения с делегатом контрвариацией является то, что в то время как делегат типа, например, Action<Fruit> может быть передан подпрограмме, ожидающей Action<Banana>, попытка объединить двух делегатов, фактические типы которых Action<Fruit> и Action<Banana> не удастся *, даже если оба делегата имеют «время компиляции» Action<Banana>. Чтобы обойти эту проблему, я бы предложил использовать метод, как следующий:

static T As<T>(this Delegate del) where T : class 
    { 
     if (del == null || del.GetType() == typeof(T)) return (T)(Object)del; 
     Delegate[] invList = ((Delegate)(Object)del).GetInvocationList(); 
     for (int i = 0; i < invList.Length; i++) 
      if (invList[i].GetType() != typeof(T)) 
       invList[i] = Delegate.CreateDelegate(typeof(T), invList[i].Target, invList[i].Method); 
     return (T)(Object)Delegate.Combine(invList); 
    } 

Учитывая делегат и тип делегата, этот метод будет проверить точно ли соответствует типу переданного в делегат указанного типа; если это не так, но метод (ы) в исходном делетете имеют соответствующие подписи для указанного типа, новый делегат будет создан с необходимыми характеристиками. Обратите внимание, что если в двух отдельных случаях эта функция передается делегатам, которые не имеют соответствующего типа, но сравниваются друг с другом, делегаты, возвращенные этим методом, также будут сравнивать друг с другом. Таким образом, если у одного есть событие, которое должно принимать делегат типа Action<string>, можно использовать вышеуказанный метод для преобразования, например. переданный Action<object> в «реальный» Action<string> перед добавлением или удалением его из события.

Если один будет добавить или вычесть переданный в делегат от поля соответствующего типа делегата, тип вывода и поведение Intellisense может быть улучшена, если один использует следующие методы:

static void AppendTo<T>(this Delegate newDel, ref T baseDel) where T : class 
    { 
     newDel = (Delegate)(Object)newDel.As<T>(); 
     T oldBaseDel, newBaseDel; 
     do 
     { 
      oldBaseDel = baseDel; 
      newBaseDel = (T)(Object)Delegate.Combine((Delegate)(object)oldBaseDel, newDel); 
     } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); 
    } 

    static void SubtractFrom<T>(this Delegate newDel, ref T baseDel) where T : class 
    { 
     newDel = (Delegate)(Object)newDel.As<T>(); 
     T oldBaseDel, newBaseDel; 
     do 
     { 
      oldBaseDel = baseDel; 
      newBaseDel = (T)(Object)Delegate.Remove((Delegate)(object)oldBaseDel, newDel); 
     } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); 
    } 

Эти методы появляются как методы расширения по типам, полученным из Delegate, и позволяют экземпляры таких типов добавлять или вычитать из переменных или полей подходящих типов делегатов; такое сложение или вычитание будет выполнено поточно-безопасным способом, поэтому возможно использование этих методов в методах добавления/удаления событий без дополнительной блокировки.

+0

Очень полезно, спасибо! –