Как получить тип обобщения в объекте?

190
17 апреля 2018, 01:44

У меня есть массив с типом EventHandler<EventBase>[]. В массиве есть объекты класса EventHandler<T>, где T - может быть любым классом, который наследуется от EventBase. Есть условие, что тип обобщения в массиве не повторяется

Мне нужно написать функцию, которая достает элемент из массива, тип обобщения которого задан типом обобщения самой функции. Например, мне надо найти объект с типом EventHandler<DamageEvent> (DamageEvent наследуется от EventBase). Для этого мне надо вот так вызвать эту функцию GetHandler<DamageEvent>()

Я бы это сделал и сам, но вот только не знаю, как узнать тип обобщения у объекта

Answer 1

Тип аргумента обобщенного класса можно узнать с помощью метода GetGenericArguments класса Type:

List<int> x = new List<int>();
var a = x.GetType().GetGenericArguments()[0];
Console.WriteLine(a.Name);

Он возвращает массив аргументов обобщения, а, так как, у вас всего один аргумент - просто возьмите из этого массива элемент с индексом 0.

Таким образом, ваш метод будет выглядеть как-то так:

EventHandler<EventBase>[] eventHandlers = new EventHandler<EventBase>[...];
EventHandler<EventBase> GetHandler<T>()
{
    return eventHandlers.Single(eh => eh.GetType().GetGenericArguments()[0] == typeof(T));
}
Answer 2

На самом деле ваша задача не имеет никакого отношения к Generics или проверкой Generic Type Arguments.

Вам нужно в массиве объектов найти экземпляры определенного типа. С этим хорошо справляется стандартный Enumerable.OfType():

EventHandler<T> GetHandler<T>()
{
    return eventHandlers.OfType<EventHandler<T>>().Single();
}

Никакие динамики или явный reflection не нужны.

И, на всякий случай - убедитесь, что в коллекции действительно лежат экземпляры нужного типа. Не уверен, что у вас происходит в коде, но со стандартным System.EventHandler<T> вы просто не сможете положить в коллекцию EventHandler<EventBase>[] экземпляр EventHandler<DamageEvent>, т.к. EventHandler от EventHandler<EventBase> не унаследован.

Т.е. код ниже не скопмилируется:

EventHandler<EventBase>[] eventHandlers = new EventHandler<EventBase>[2];
eventHandlers[0] = SomeHandler1;
eventHandlers[1] = new EventHandler<DamageEvent>(SomeHandler2);

и вам придется хранить хэндлеры как object[]

Answer 3

Если скорость не важна, то можно выполнить фокус с dynamic.

Например, есть функция

public void ProcEvent<T>(T[] events)
{
    foreach(var e in events)
        ProcEvent((dynamic)e);
}

И 3 перегрузки вызываемой функции, своя для каждого типа

public void ProcEvent(object @event)
{
    Console.WriteLine("Object called");
}
public void ProcEvent(EventHandler<DamageEvent> @event)
{
    Console.WriteLine("DamageEvent called");
}
public void ProcEvent(EventHandler<EventBase> @event)
{
    Console.WriteLine("EventBase called");
}

Работу можно проверить так:

var events = new object[] { 
    new EventHandler<EventBase>((s, e) => {}) ,
    new EventHandler<EventArgs>((s, e) => {}) ,
    new EventHandler<DamageEvent>((s, e) => {}) ,
};
ProcEvent(events);

Вывод ожидаемо такой

EventBase called
Object called
DamageEvent called

Плюсы: код короче

Минусы: код медленный, ибо рефлексия. Код чуточку запутанный, ибо рефлексия :)

READ ALSO
Проблема с десериализацией из XML

Проблема с десериализацией из XML

Помогите разобраться с десериализацией

167
C# RichTextBox странное поведение курсора над текстом

C# RichTextBox странное поведение курсора над текстом

Имею простое приложение c# Windows formДалее есть единственная форма с контролом richtextbox, который закреплен на всей форме(по размеру)

194
Entity-framework анонимные объекты

Entity-framework анонимные объекты

Есть 2 моделькиКоманда и тренер

184