在 C# 中转换为泛型类型

Posted

技术标签:

【中文标题】在 C# 中转换为泛型类型【英文标题】:Cast to generic type in C# 【发布时间】:2009-06-16 18:02:04 【问题描述】:

我有一个字典来将某种类型映射到该类型的某个通用对象。例如:

typeof(LoginMessage) maps to MessageProcessor<LoginMessage>

现在的问题是在运行时从字典中检索这个通用对象。或者更具体一点:将检索到的对象转换为特定的泛型类型。

我需要它来做这样的事情:

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

希望有一个简单的解决方案。

编辑: 我不想使用 Ifs 和开关。由于性能问题,我也不能使用某种反射。

【问题讨论】:

以下问题有一个使用泛型避免的强制转换场景 - Refactoring Code to avoid Type Casting 【参考方案1】:

以下似乎也有效,并且比其他答案短一点:

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

【讨论】:

如果有任何具体原因被否决,我很想知道为什么 - 这是不好的做法,不起作用还是其他原因? 我猜这被否决了,因为这绝对是错误的。转换不是强制转换。 msdn.microsoft.com/en-us/library/dtb69x08.aspx 它只有在你实现 IConvertible 时才有效,正如你所看到的,IConvertible 只定义了对 CLR 类型的转换:msdn.microsoft.com/en-us/library/system.iconvertible.aspx 啊,我不明白其中的区别,只是知道它对我有用。感谢您的解释。 要解决 IConvertible 的问题,只需实现一个过滤类型的方法,如下所示: public T ConvertObject(object sourceObject) where T : IConvertible return (T)Convert.ChangeType(源对象,类型(T)); 【参考方案2】:

这对你有用吗?

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 接口。实际的处理代码现在应该成为应该实现这个接口的不同消息类的一部分。

【讨论】:

不,因为我需要具有类型安全泛型方法的特定泛型实例。可悲的是,基类无法解决问题;) 我添加了一个示例,说明如何将抽象方法添加到抽象类并在具体类中覆盖它。 我希望避免在运行时进行类型检查,但正如大家所说,这似乎是不可避免的。 ;) 所以我将不得不采用这种方法。谢谢。 @Jeroen:这不正确。您将 typeof(string) 映射到其各自的 MessageProcessor,但消息的类型将是 Message&lt;string&gt;,而不是 string。这个想法是正确的。 @Andrey:如果您的消息实现了提供实际处理的接口,则可能不会。【参考方案3】:

我遇到了类似的问题。我有课;

Action<T>

具有类型 T 的属性。

当我不知道 T 时,我如何获得该属性?除非我知道 T,否则我无法投射到 Action。

解决方案:

实现一个非泛型接口;

public interface IGetGenericTypeInstance

    object GenericTypeInstance();

现在我可以将对象转换为 IGetGenericTypeInstance 并且 GenericTypeInstance 会将属性作为类型对象返回。

【讨论】:

【参考方案4】:
Type type = typeof(MessageProcessor<>).MakeGenericType(key);

这是你能做的最好的事情,但是在不知道它是什么类型的情况下,你真的无能为力。

编辑:我应该澄清一下。我从 var 类型更改为 Type 类型。我的意思是,现在你可以这样做:

object obj = Activator.CreateInstance(type);

obj 现在将是正确的类型,但是由于您在编译时不知道“key”是什么类型,因此无法对其进行强制转换并对其进行任何有用的操作。

【讨论】:

行不通。 var 只是在编译时解析的语法糖。 type 变量的类型将是 System.Type 是的,我知道。它将是 MessageProcessor 的类型,然后您可以将其与 Activator.CreateInstance() 一起使用。我的观点是,在那个时候,你不能用它做很多事情,因为你不知道“key”是什么类型。【参考方案5】:

您可以编写一个将类型作为泛型参数的方法:

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

【讨论】:

如前所述,由于性能问题,我想避免使用反射。这些方法可能会在很短的时间内被调用数百次,并且仍然需要非常快地执行。 为了优化这一点,您可以设置它,以便每个消息类型只进行一次反射。您可以创建一个调用泛型方法的委托并将其存储在字典中,因此下次出现该类型时,它只需调用该委托。最简单的方法可能是编译一个 lambda 表达式。 再一次,Andrej,你的问题要提一下。【参考方案6】:

请查看以下解决方案是否适合您。诀窍是定义一个基本处理器接口,它采用基本类型的消息。

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

【讨论】:

【参考方案7】:

你不能那样做。您可以尝试从更高级别的角度(即您想使用转换变量完成什么)来解决您的问题,以获得不同的解决方案。

你可以这样做:

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

【讨论】:

如果这不可能,是否有另一种方法可以将类型映射到某个泛型类型? @Andrej:“地图”在这里有点模糊。你能告诉我们你想用强制转换的变量做什么吗?由于在编译时类型是未知的,因此您不能在 in 左右调用方法,除非您硬编码不同的 key 类型... 我有某些消息(LoginMessage、SayMessage、...)。每个消息都必须由特定的 MessageProcessor 处理,该 MessageProcessor 具有类似“ProcessMessage(MessageSource source, MessageType message)”的方法。我当然可以使用 Messages 的基类而不是通用方法,但我希望这是类型安全的,以便特定的处理器只能处理他应该处理的消息。 @Andrej:基类方法仍然可以是“类型安全的”,但不会在编译时强制执行类型安全。您无法在编译时执行此操作,因为无论如何在编译时都不知道类型。我想这是你最好的选择。 您能详细说明一下吗?我不知道怎么可能不将 LoginMessage 传递给 SayMessageProcessor。这就是我想要避免的。【参考方案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

...

【讨论】:

我使用这个结构来完全避免Ifs和switches。所以这是不行的。 然后把它放在你的问题中。【参考方案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 之前的回答一般都可以,但是泛型方法必须是 public 或者必须使用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>();

【讨论】:

【参考方案13】:

要将任何类型对象转换为泛型类型T,诀窍是首先分配给任何更高类型的对象,然后将其强制转换为泛型类型。

object temp = otherTypeObject;
T result = (T)temp;

【讨论】:

以上是关于在 C# 中转换为泛型类型的主要内容,如果未能解决你的问题,请参考以下文章

Java泛型详解

如何在 typescript 中为泛型类编写扩展作为 getter

使用反射为泛型类创建构造函数

C#泛型 类型约束

Java:将对象转换为泛型类型

编写高质量代码改善C#程序的157个建议——建议45:为泛型类型参数指定逆变