Cast to generic type in C#

前端 未结 13 895
野性不改
野性不改 2021-01-30 20:27

I have a Dictionary to map a certain type to a certain generic object for that type. For example:

typeof(LoginMessage) maps to MessageProcessor

        
相关标签:
13条回答
  • 2021-01-30 21:07
    Type type = typeof(MessageProcessor<>).MakeGenericType(key);
    

    That's the best you can do, however without actually knowing what type it is, there's really not much more you can do with it.

    EDIT: I should clarify. I changed from var type to Type type. My point is, now you can do something like this:

    object obj = Activator.CreateInstance(type);
    

    obj will now be the correct type, but since you don't know what type "key" is at compile time, there's no way to cast it and do anything useful with it.

    0 讨论(0)
  • 2021-01-30 21:10

    I struggled to solve a similar problem around data table classes instead of messages. The root issue mentioned above of casting a non-generic version of the class to a derived generic version was the same.

    In order to allow injection into a portable class library which did not support database libraries, I introduced a set of interface classes, with the intent that I could pass a type and get a matching generic. It ended up needing to implement a generic method.

    // 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>();
    }
    

    And this the whole implementation using the generic method above.

    The generic class that will be cast from a dictionary.

    // 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))
        {
        }
    }
    

    This is the class that stores the generic class and casts it to satisfy the generic method in the interface

    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>;
        }
    }
    

    And finally the calling of the interface method.

    IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>();
    
    0 讨论(0)
  • 2021-01-30 21:11

    You can write a method that takes the type as a generic parameter:

    void GenericProcessMessage<T>(T message)
    {
        MessageProcessor<T> processor = messageProcessors[typeof(T)]
            as MessageProcessor<T>;
    
        //  Call method processor or whatever you need to do
    }
    

    Then you need a way to call the method with the correct generic argument. You can do this with reflection:

    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});
    }
    
    0 讨论(0)
  • 2021-01-30 21:12

    Please see if following solution works for you. The trick is to define a base processor interface which takes a base type of message.

    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();
        }
    }
    
    0 讨论(0)
  • 2021-01-30 21:17

    I had a similar problem. I have a class;

    Action<T>
    

    which has a property of type T.

    How do I get the property when I don't know T? I can't cast to Action<> unless I know T.

    SOLUTION:

    Implement a non-generic interface;

    public interface IGetGenericTypeInstance
    {
        object GenericTypeInstance();
    }
    

    Now I can cast the object to IGetGenericTypeInstance and GenericTypeInstance will return the property as type object.

    0 讨论(0)
  • 2021-01-30 21:21

    Does this work for you?

    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);
        }
    }
    

    This gives you the correct object. The only thing I am not sure about is whether it is enough in your case to have it as an abstract MessageProcessor.

    Edit: I added an IMessage interface. The actual processing code should now become part of the different message classes that should all implement this interface.

    0 讨论(0)
提交回复
热议问题