web-dev-qa-db-ja.com

C#でジェネリック型にキャストする

特定のタイプをそのタイプの特定の汎用オブジェクトにマップする辞書があります。例えば:

typeof(LoginMessage) maps to MessageProcessor<LoginMessage>

問題は、実行時にこの汎用オブジェクトをディクショナリから取得することです。または、より具体的に:取得したオブジェクトを特定のジェネリック型にキャストします。

私はこのようなことをする必要があります:

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

これに対する簡単な解決策があることを願っています。

編集:Ifとスイッチを使用しません。パフォーマンスの問題のため、何らかのリフレクションも使用できません。

37
Andrej

これはあなたのために働きますか?

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インターフェースを追加しました。実際の処理コードは、このインターフェイスをすべて実装するさまざまなメッセージクラスの一部になります。

29
Jeroen Huinink

以下も同様に機能するようで、他の回答よりも少し短くなっています。

T result = (T)Convert.ChangeType(otherTypeObject, typeof(T));
21
ptrc
Type type = typeof(MessageProcessor<>).MakeGenericType(key);

それはあなたができる最善のことですが、実際にそれがどんな型であるかを知らなくても、あなたがそれでできることは本当に多くありません。

編集:明確にする必要があります。 var typeからType typeに変更しました。私のポイントは、次のようなことができるようになったことです。

object obj = Activator.CreateInstance(type);

objは正しい型になりますが、コンパイル時に「キー」の型がわからないため、キャストして有用なことを行う方法はありません。

9
BFree

同様の問題がありました。クラスがあります。

Action<T>

タイプTのプロパティがあります。

Tがわからない場合、どのようにしてプロパティを取得しますか? Tを知らない限り、Action <>にキャストできません。

解決策:

非ジェネリックインターフェイスを実装します。

public interface IGetGenericTypeInstance
{
    object GenericTypeInstance();
}

これで、オブジェクトをIGetGenericTypeInstanceにキャストでき、GenericTypeInstanceはプロパティを型オブジェクトとして返します。

8
Casey Burns

ジェネリックパラメーターとして型を受け取るメソッドを作成できます。

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});
}
8
Daniel Plaisted

次の解決策が有効かどうかを確認してください。トリックは、メッセージの基本タイプを取得する基本プロセッサインターフェイスを定義することです。

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

それはできません。別のソリューションのために、より高いレベルの観点(つまり、キャストされた変数で正確に何を達成したいのか)から問題を伝えることができます。

このようなもので行くことができます:

 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);
4
Mehrdad Afshari

これは単に許可されていません:

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
}
...
3
Colin Burnett

前述のように、直接キャストすることはできません。考えられる解決策の1つは、これらのジェネリック型を非ジェネリックインターフェイスから継承させることです。この場合、リフレクションなしでメソッドを呼び出すことができます。リフレクションを使用すると、マッピングされたオブジェクトを期待する任意のメソッドに渡すことができ、キャストが実行されます。したがって、パラメータとしてMessageProcessorを受け取るAcceptというメソッドがある場合、それを見つけて動的に呼び出すことができます。

1
eulerfx
    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);
            }
        }
    }
1
wazazhang

前の@DanielPlaistedの答えは一般に機能しますが、一般的なメソッドはパブリックであるか、BindingFlags.NonPublic | BindingFlags.Instanceを使用する必要があります!評判不足のコメントとして投稿できませんでした。

0
Peter

メッセージではなくデータテーブルクラスに関する同様の問題を解決するのに苦労しました。クラスの非ジェネリックバージョンを派生ジェネリックバージョンにキャストするという上記の根本的な問題は同じでした。

データベースライブラリをサポートしていないポータブルクラスライブラリへの注入を許可するために、型を渡し、一致するジェネリックを取得できるように、一連のインターフェイスクラスを導入しました。最終的には汎用メソッドを実装する必要がありました。

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