web-dev-qa-db-ja.com

C#の汎用インターフェイスとファクトリパターン

メソッドの1つのパラメータタイプがジェネリックによって定義されるジェネリックインターフェイスを作成しようとしています

[〜#〜] edit [〜#〜]

Factory作成メソッドでtypeパラメーターを指定することで問題を混乱させた可能性があることに気付いた後、質問を少し変更しました。私が持っているのは、サードパーティAPIに対して行う必要がある2種類のAPI呼び出しです。最初は、intであるIDを使用してAPIからレコードを取得します。 2番目もAPIからレコードを取得しますが、IDは文字列(guid)です。各レコードタイプ(ClientEntityとInvoiceEntity)のクラスがあり、どちらもIdタイプを渡すジェネリックインターフェイスを実装しています

これは、idパラメーターを持つメソッドを宣言するインターフェイスです

public interface IGeneric<TId>
{
    void ProcessEntity(TId id);
}

インターフェイスをいくつかのクラスに実装します。1つはidをintに設定し、もう1つは文字列に設定します。

public class ClientEntity: IGeneric<int> // Record with Id that is an int
{
    public void ProcessEntity(int id)
    {
        Console.WriteLine(id);
        // call 3rd party API with int Id
    }
}

public class InvoiceEntity: IGeneric<string> // Record with Id that is a string (guid)
{
    public void ProcessEntity(string id)
    {
        Console.WriteLine(id);
        // call 3rd party API with string Id
    }
}

私が知りたいのは、これをファクトリーパターン内でどのように使用するのですか?

public static class GenericFactory
{
    public static IGeneric<WhatGoesHere> CreateGeneric(string recordType)
    {
        if (recordType == "Client")
        {
            return new ClientEntity();
        }
        if (type == "Invoice")
        {
            return new InvoiceEntity();
        }

        return null;
    }

}

目的は、ProcessEntityメソッドを呼び出すことができるように、ファクトリを使用して正しいクラスをインスタンス化することです

[〜#〜] edit [〜#〜]

ファクトリによって作成されたクラスがそれを処理する必要があるため、ジェネリック型をファクトリメソッドに渡す必要はありません。オブジェクトを作成するとき、どのタイプのIDが必要かわからないので、ファクトリにそれを処理させたい

例えば.

   var myGeneric = GenericFactory.CreateGeneric("Client");
   myGeneric.ProcessEntity("guid")

または

   var myGeneric = GenericFactory.CreateGeneric("Invoice");
   myGeneric.ProcessEntity(1234)

それが理にかなっていることを願っています

12
Nick Smith

次のようなことができるはずです。

_public static class GenericFactory
{
    public static IGeneric<T> CreateGeneric<T>()
    {
        if (typeof(T) == typeof(string))
        {
            return (IGeneric<T>) new GenericString();
        }

        if (typeof(T) == typeof(int))
        {
            return (IGeneric<T>) new GenericInt();
        }

        throw new InvalidOperationException();
    }
}
_

次のように使用します。

_var a = GenericFactory.CreateGeneric<string>();
var b = GenericFactory.CreateGeneric<int>();
_

これは、型名を文字列として渡すのではなく、厳密に型指定された呼び出しを使用することに注意してください(実際に必要な場合とそうでない場合があります)。


代わりに型名に文字列を渡したい場合、実際の型を返す方法がないため、objectを返す必要があります。

_public static object CreateGeneric(string type)
{
    switch (type)
    {
        case "string": return new GenericString();
        case "int":    return new GenericInt();
        default:       throw new InvalidOperationException("Invalid type specified.");
    }
}
_

明らかにobjectがある場合、それを使用するには適切な型にキャストする必要があります(実際の型を知っている必要があります)。

あるいは、リフレクションを使用して、リフレクションに含まれるメソッドを判別し、その方法で呼び出すことができます。ただし、正しい型のパラメーターを渡すには、その型を知る必要があります。

ここであなたがしようとしていることは正しいアプローチではないと思います。それを使用しようとすると、あなたはそれを発見するでしょう。

ハッキングソリューション:dynamicを使用

それにもかかわらず、あなたが望むものに近いものを得ることができる1つの方法があります:dynamicを次のように使用します(上記のobject CreateGeneric(string type)ファクトリーメソッドを使用していると仮定して):

_dynamic a = GenericFactory.CreateGeneric("string");
dynamic b = GenericFactory.CreateGeneric("int");

a.ProcessEntity("A string");
b.ProcessEntity(12345);
_

dynamicは、バックグラウンドでリフレクションとコード生成を使用するため、最初の呼び出しが比較的遅くなる可能性があることに注意してください。

また、dynamicを介してアクセスされるメソッドに間違った型を渡すと、厄介な実行時例外が発生することに注意してください。

_dynamic a = GenericFactory.CreateGeneric("string");
a.ProcessEntity(12345); // Wrong parameter type!
_

そのコードを実行すると、次のようなランタイム例外が発生します。

_Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: The best overloaded method match for 'ConsoleApplication1.GenericString.ProcessEntity(string)' has some invalid arguments
   at CallSite.Target(Closure , CallSite , Object , Int32 )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
   at ConsoleApplication1.Program.Main() in D:\Test\CS6\ConsoleApplication1\Program.cs:line 71
_
11
Matthew Watson

通常、DIコンテナ(DIはGenericIntまたはGenericStringに依存関係がある場合など)を使用するそのファクトリに対してですが、これをどのように解決できるかを単に示すために:

void Main()
{
    GenericFactory.CreateGeneric<int>();
    GenericFactory.CreateGeneric<string>();
}

public static class GenericFactory
{
    private static Dictionary<Type, Type> registeredTypes = new Dictionary<System.Type, System.Type>();

    static GenericFactory()
    {
        registeredTypes.Add(typeof(int), typeof(GenericInt));
        registeredTypes.Add(typeof(string), typeof(GenericString));
    }

    public static IGeneric<T> CreateGeneric<T>()
    {
        var t = typeof(T);
        if (registeredTypes.ContainsKey(t) == false) throw new NotSupportedException();

        var typeToCreate = registeredTypes[t];
        return Activator.CreateInstance(typeToCreate, true) as IGeneric<T>;
    }

}

public interface IGeneric<TId>
{
    TId Id { get; set; }

    void ProcessEntity(TId id);
}

public class GenericInt : IGeneric<int>
{
    public int Id { get; set; }

    public void ProcessEntity(int id)
    {
        Console.WriteLine(id);
    }
}

public class GenericString : IGeneric<string>
{
    public string Id { get; set; }

    public void ProcessEntity(string id)
    {
        Console.WriteLine(id);
    }
}
14
tym32167

Staticクラスを使用したい場合は正しいとマークされた答えは問題ありませんが、オブジェクトを更新する代わりにDIを注入した型を返したい場合はどうでしょうか?以下をお勧めします!

public interface IGenericFactory
{
    IGeneric<T> GetGeneric<T>() where T : class;
}

public class GenericFactory: IGenericFactory
{
    private readonly IGeneric<int> intGeneric;
    private readonly IGeneric<string> stringGeneric;
    public GenericFactory(IGeneric<int> intG, IGeneric<string> stringG)
    {
        intGeneric = intG;
        stringG = stringG;
    }

    public IGeneric<T> GetGeneric<T>() where T : class
    {
        if (typeof(T) == typeof(IGeneric<int>))
            return (IGeneric<T>)Convert.ChangeType(intGeneric, typeof(IGeneric<T>));

        if (typeof(T) == typeof(IGeneric<string>))
            return (IGeneric<T>)Convert.ChangeType(stringGeneric,typeof(IGeneric<T>));
        else 
            throw new NotSupportedException();
    }
}

コンストラクタを明確にするために、2つの期待される戻り値の型を単純に挿入したことに注意してください。ファクトリをディクショナリとして実装し、戻り値オブジェクトをこのディクショナリに注入することもできます。それが役に立てば幸い。

1
Dzidzai