Подтвердить что ты не робот

Приведение в общий тип в С#

У меня есть словарь для сопоставления определенного типа определенному универсальному объекту для этого типа. Например:

typeof(LoginMessage) maps to MessageProcessor<LoginMessage>

Теперь проблема состоит в том, чтобы получить этот универсальный объект во время выполнения из Словаря. Или, если быть более конкретным: направить восстановленный объект на определенный тип общего типа.

Мне нужно, чтобы он работал примерно так:

Type key = message.GetType();
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>;

Надеюсь, что это будет легкое решение.

Изменить: Я не хочу использовать Ifs и переключатели. Из-за проблем с производительностью я также не могу использовать какое-либо отражение.

4b9b3361

Ответ 1

Это работает для вас?

interface IMessage
{
    void Process(object source);
}

class LoginMessage : IMessage
{
    public void Process(object source)
    {
    }
}

abstract class MessageProcessor
{
    public abstract void ProcessMessage(object source, object type);
}

class MessageProcessor<T> : MessageProcessor where T: IMessage
{
    public override void ProcessMessage(object source, object o) 
    {
        if (!(o is T)) {
            throw new NotImplementedException();
        }
        ProcessMessage(source, (T)o);
    }

    public void ProcessMessage(object source, T type)
    {
        type.Process(source);
    }
}


class Program
{
    static void Main(string[] args)
    {
        Dictionary<Type, MessageProcessor> messageProcessors = new Dictionary<Type, MessageProcessor>();
        messageProcessors.Add(typeof(string), new MessageProcessor<LoginMessage>());
        LoginMessage message = new LoginMessage();
        Type key = message.GetType();
        MessageProcessor processor = messageProcessors[key];
        object source = null;
        processor.ProcessMessage(source, message);
    }
}

Это дает вам правильный объект. Единственное, что я не уверен в том, достаточно ли в вашем случае иметь его как абстрактный MessageProcessor.

Изменить: я добавил интерфейс IMessage. Фактический код обработки теперь должен стать частью различных классов сообщений, которые должны реализовать весь этот интерфейс.

Ответ 2

Следующее, похоже, работает, и оно немного короче, чем другие ответы:

T result = (T)Convert.ChangeType(otherTypeObject, typeof(T));

Ответ 3

Вы можете написать метод, который принимает тип как общий параметр:

void GenericProcessMessage<T>(T message)
{
    MessageProcessor<T> processor = messageProcessors[typeof(T)]
        as MessageProcessor<T>;

    //  Call method processor or whatever you need to do
}

Затем вам нужен способ вызова метода с правильным общим аргументом. Вы можете сделать это с отражением:

public void ProcessMessage(object message)
{
    Type messageType = message.GetType();
    MethodInfo method = this.GetType().GetMethod("GenericProcessMessage");
    MethodInfo closedMethod = method.MakeGenericMethod(messageType);
    closedMethod.Invoke(this, new object[] {message});
}

Ответ 4

Type type = typeof(MessageProcessor<>).MakeGenericType(key);

Это лучшее, что вы можете сделать, однако, не зная, какой тип это, действительно, вы не можете сделать больше с ним.

EDIT: Я должен уточнить. Я изменил с типа var на тип типа. Я хочу сказать, теперь вы можете сделать что-то вроде этого:

object obj = Activator.CreateInstance(type);

obj теперь будет правильным типом, но так как вы не знаете, какой тип "ключ" находится во время компиляции, нет способа его бросить и сделать что-нибудь полезное с ним.

Ответ 5

У меня была аналогичная проблема. У меня есть класс;

Action<T>

обладающее свойством типа T.

Как получить свойство, когда я не знаю T? Я не могу передать в Action < > если я не знаю T.

РЕШЕНИЕ:

Внедрить не общий интерфейс;

public interface IGetGenericTypeInstance
{
    object GenericTypeInstance();
}

Теперь я могу передать объект в IGetGenericTypeInstance, а GenericTypeInstance вернет свойство как объект типа.

Ответ 6

Вы не можете этого сделать. Вы можете попытаться рассказать о своей проблеме с более высокой точки зрения (то есть, что именно вы хотите выполнить с литой переменной) для другого решения.

Вы можете пойти с чем-то вроде этого:

 public abstract class Message { 
     // ...
 }
 public class Message<T> : Message {
 }

 public abstract class MessageProcessor {
     public abstract void ProcessMessage(Message msg);
 }
 public class SayMessageProcessor : MessageProcessor {
     public override void ProcessMessage(Message msg) {
         ProcessMessage((Message<Say>)msg);
     }
     public void ProcessMessage(Message<Say> msg) {
         // do the actual processing
     }
 }

 // Dispatcher logic:
 Dictionary<Type, MessageProcessor> messageProcessors = {
    { typeof(Say), new SayMessageProcessor() },
    { typeof(string), new StringMessageProcessor() }
 }; // properly initialized

 messageProcessors[msg.GetType().GetGenericArguments()[0]].ProcessMessage(msg);

Ответ 7

Посмотрите, подходит ли вам следующее решение. Трюк заключается в определении интерфейса базового процессора, который принимает базовый тип сообщения.

interface IMessage
{
}

class LoginMessage : IMessage
{
}

class LogoutMessage : IMessage
{
}

class UnknownMessage : IMessage
{
}

interface IMessageProcessor
{
    void PrcessMessageBase(IMessage msg);
}

abstract class MessageProcessor<T> : IMessageProcessor where T : IMessage
{
    public void PrcessMessageBase(IMessage msg)
    {
        ProcessMessage((T)msg);
    }

    public abstract void ProcessMessage(T msg);

}

class LoginMessageProcessor : MessageProcessor<LoginMessage>
{
    public override void ProcessMessage(LoginMessage msg)
    {
        System.Console.WriteLine("Handled by LoginMsgProcessor");
    }
}

class LogoutMessageProcessor : MessageProcessor<LogoutMessage>
{
    public override void ProcessMessage(LogoutMessage msg)
    {
        System.Console.WriteLine("Handled by LogoutMsgProcessor");
    }
}

class MessageProcessorTest
{
    /// <summary>
    /// IMessage Type and the IMessageProcessor which would process that type.
    /// It can be further optimized by keeping IMessage type hashcode
    /// </summary>
    private Dictionary<Type, IMessageProcessor> msgProcessors = 
                                new Dictionary<Type, IMessageProcessor>();
    bool processorsLoaded = false;

    public void EnsureProcessorsLoaded()
    {
        if(!processorsLoaded)
        {
            var processors =
                from processorType in Assembly.GetExecutingAssembly().GetTypes()
                where processorType.IsClass && !processorType.IsAbstract &&
                      processorType.GetInterface(typeof(IMessageProcessor).Name) != null
                select Activator.CreateInstance(processorType);

            foreach (IMessageProcessor msgProcessor in processors)
            {
                MethodInfo processMethod = msgProcessor.GetType().GetMethod("ProcessMessage");
                msgProcessors.Add(processMethod.GetParameters()[0].ParameterType, msgProcessor);
            }

            processorsLoaded = true;
        }
    }

    public void ProcessMessages()
    {
        List<IMessage> msgList = new List<IMessage>();
        msgList.Add(new LoginMessage());
        msgList.Add(new LogoutMessage());
        msgList.Add(new UnknownMessage());

        foreach (IMessage msg in msgList)
        {
            ProcessMessage(msg);
        }
    }

    public void ProcessMessage(IMessage msg)
    {
        EnsureProcessorsLoaded();
        IMessageProcessor msgProcessor = null;
        if(msgProcessors.TryGetValue(msg.GetType(), out msgProcessor))
        {
            msgProcessor.PrcessMessageBase(msg);
        }
        else
        {
            System.Console.WriteLine("Processor not found");
        }
    }

    public static void Test()
    {
        new MessageProcessorTest().ProcessMessages();
    }
}

Ответ 8

Это просто не разрешено:

Type key = message.GetType();
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>;

Вы не можете получить общий тип как значение переменной.

Вам нужно было бы сделать переключатель или что-то еще:

Type key = message.GetType();
if (key == typeof(Foo))
{
    MessageProcessor<Foo> processor = (MessageProcessor<Foo>)messageProcessors[key];
    // Do stuff with processor
}
else if (key == typeof(Bar))
{
    MessageProcessor<bar> processor = (MessageProcessor<Bar>)messageProcessors[key];
    // Do stuff with processor
}
...

Ответ 9

Как уже упоминалось, вы не можете использовать его напрямую. Одно из возможных решений заключается в том, чтобы эти родовые типы наследовались от не-общего интерфейса, и в этом случае вы все равно можете ссылаться на методы без него. Используя отражение, вы можете передать отображаемый объект любому ожидающему его методу, тогда выполнение будет выполнено для вас. Поэтому, если у вас есть метод под названием Accept, ожидающий MessageProcessor как параметр, вы можете найти его и вызвать его динамически.

Ответ 10

    public delegate void MessageProcessor<T>(T msg) where T : IExternalizable;


    virtual public void OnRecivedMessage(IExternalizable msg)
    {
        Type type = msg.GetType();
        ArrayList list = processors.Get(type);
        if (list != null)
        {
            object[] args = new object[]{msg};
            for (int i = list.Count - 1; i >= 0; --i)
            {
                Delegate e = (Delegate)list[i];
                e.Method.Invoke(e.Target, args);
            }
        }
    }

Ответ 11

Ответ @DanielPlaisted до работы обычно работает, но общий метод должен быть общедоступным или нужно использовать BindingFlags.NonPublic | BindingFlags.Instance! Не удалось опубликовать его в качестве комментария за отсутствие репутации.

Ответ 12

Я попытался решить подобную проблему вокруг классов таблиц данных вместо сообщений. Корневая проблема, упомянутая выше, в том, что отличная версия класса для генерируемой версии не была общей, была одинаковой.

Чтобы включить инъекцию в переносимую библиотеку классов, которая не поддерживает библиотеки баз данных, я представил набор интерфейсных классов с намерением, чтобы я мог передать тип и получить соответствующий общий. Это привело к необходимости использования универсального метода.

// Interface for injection
public interface IDatabase
{
    // Original, non-functional signature:
    IDatatable<object> GetDataTable(Type dataType);

    // Functional method using a generic method:
    IDatatable<T> GetDataTable<T>();
}

И это вся реализация с использованием общего метода выше.

Общий класс, который будет выведен из словаря.

// Non-generic base class allows listing tables together
abstract class Datatable
{
    Datatable(Type storedClass)
    {
      StoredClass = storedClass;
    }

    Type StoredClass { get; private set; }
}

// Generic inheriting class
abstract class Datatable<T>: Datatable, IDatatable<T>
{
    protected Datatable()
        :base(typeof(T))
    {
    }
}

Это класс, который хранит общий класс и передает его для удовлетворения общего метода в интерфейсе

class Database
{
    // Dictionary storing the classes using the non-generic base class
    private Dictionary<Type, Datatable> _tableDictionary;

    protected Database(List<Datatable> tables)
    {
        _tableDictionary = new Dictionary<Type, Datatable>();
        foreach (var table in tables)
        {
            _tableDictionary.Add(table.StoredClass, table);
        }
    }

    // Interface implementation, casts the generic
    public IDatatable<T> GetDataTable<T>()
    {
        Datatable table = null;

        _tableDictionary.TryGetValue(typeof(T), out table);

        return table as IDatatable<T>;
    }
}

И, наконец, вызов метода интерфейса.

IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>();