2

ジェネリックを使用してファクトリ/ビルダーを提供することにより、インターフェイスの実装を抽象化しようとしています。ただし、実行時に複数のディスパッチと C# ジェネリックで問題が発生し、奇妙なことを行っています。

基本的なシナリオは、いくつかのインターフェイスを定義したことです。

public interface IAddressModel
{
}

public interface IUserModel
{
}

次に、実際の実装を返すファクトリ クラスがあります。

public class Factory
{
    public T BuildModel<T>()
    {
        return BuildModel(default(T));
    }

    public object BuildModel(object obj)
    {
        //this is here because the compiler will complain about casting T to
        //the first inteface parameter in the first defined BuildModel method
        return null;
    }

    public IAddressModel BuildModel(IAddressModel iModel)
    {
        //where AddressModel inherits from IAddressModel
        return new AddressModel();
    }

    public IUserModel BuildModel(IUserModel iModel)
    {
        //where UserModel inherits from IUserModel
        return new UserModel(); 
    }
}

問題は、ファクトリが次new Factory().BuildModel<IAddressModel>() のように呼び出される場合です。実行時にジェネリックからディスパッチされる BuildModel(...) メソッドは、常に T の最小派生形式であり、この場合は常にオブジェクトです。

ただし、new Factory().BuildModel(default(IAddressModel));正しいメソッドを呼び出すと、ディスパッチされます (これはおそらくコンパイル時に行われるためです)。ジェネリックを使用した動的ディスパッチは、コンパイル時または実行時に呼び出されるメソッドが同じである必要があるにもかかわらず、最も派生した型のメソッドをチェックしないようです。理想的には、BuildModel(...) メソッドを非公開にして、ジェネリック メソッドのみを公開したいと考えています。動的ディスパッチが実行時に正しいメソッドを呼び出すようにする別の方法はありますか? BuildModel<>()実装を変更しようとしましreturn BuildModel((dynamic)default(T))たが、これにより、ディスパッチするメソッドを決定できないという実行時エラーがスローされます。反変性とより多くのインターフェースでこれを行う方法はありますか?

4

2 に答える 2

2

引数のタイプに基づいて、自分でディスパッチを実行できる場合がありますT

public class Factory
{
    private Dictionary<Type, Func<object>> builders = new Dictionary<Type, Func<object>>
    {
        { typeof(IAddressModel), BuildAddressModel },
        { typeof(IUserModel), BuildUserModel }
    };

    public T Build<T>()
    {
        Func<object> buildFunc;
        if (builders.TryGetValue(typeof(T), out buildFunc))
        {
            return (T)buildFunc();
        }
        else throw new ArgumentException("No builder for type " + typeof(T).Name);
    }

    private static IAddressModel BuildAddressModel()
    {
        return new AddressModel();
    }

    private static IUserModel BuildUserModel()
    {
        return new UserModel();
    }
}
于 2013-02-12T21:13:28.810 に答える
1

コードの現在の状態をコンパイルするには、明示的なキャストが必要です。

public T BuildModel<T>()
{
    return (T)BuildModel(default(T));
}

BuildModel は T を多態的にオブジェクトとして扱っています。そのような制限を定義しない限りBuildModel、 T が anIAddressModelまたは anであることを認識しません。IUserModel

public T BuildModel<T>() where T: IAddressModel
{            
    Console.WriteLine(typeof(T));
    return (T)BuildModel(default(T));
}

これで、コンパイラは T が であることを認識するのに十分な情報を取得しましたIAddressModel。しかし、あなたが求めobjectているのは、タイプセーフではないより派生したパラメーター (共変) になることです。つまり、C# はタイプ セーフではないため、共変パラメーターの型をサポートしていません。

条件付きロジックを使用して、ファクトリのような動作を実現できます。

    public T BuildModel<T>()
    {
        T result = default(T);

        if (typeof(T) == typeof(IAddressModel))
            result = (T)BuildModel((IAddressModel)result);
        else if (typeof(T) == typeof(IUserModel))
            result = (T)BuildModel((IUserModel)result);

        return result;
    }
于 2013-02-12T21:04:26.610 に答える