1

このコードがあるとしましょう

public interface IFoo
{
}

public abstract class FooBase<TModel> : IFoo
{
    public T Create<T>() where T : TModel;
}

public class Foo : FooBase<ModelBase>
{
    public TModel Create<TModel>()
    {
        return Activator.CreateInstance(typeof(TModel));
    }
}

public abstract class ModelBase
{
}

public class ModelFoo : ModelBase
{
}

public class ModelBar : ModelBase
{
}

さて、私はこのクラスを持っているとしましょう

public static FooProvider
{
    public IFoo Get<TModel>()
    {
        var provider = ...; // find which IFoo class has generic TModel
        return provider;
    }
}

そして、私はこれを呼び出すことができます

IFoo provider = FooProvider.Get<ModelBar>();  // -> instance of Foo

しかし、私は のインスタンスを取得するため、メソッドIFooにアクセスできません。FooBase(または中間抽象クラス)を実装してIFoo、(必ずしも) 戻り値をキャストせずに呼び出すことができるメソッド宣言を提供することは可能providerですか?

理想的には、私はできるようになりたいです

ModelBar bar = FooProvider.Get<ModelBar>().Create<ModelBar>();

出来ますか?

4

2 に答える 2

2

質問が進化するにつれて、2番目の回答を投稿しています。以下は、サンプルのクラス階層です。

  • 多くのモデル タイプに同じプロバイダーを使用できます
  • 一貫した方法で多くの異なるプロバイダーとモデルを構築できます
  • コンパイル時にプロバイダー/モデルの互換性をチェックします
  • 使用中にキャストする必要はありません

コード:

public interface IWhatever { }

public class Foo { }
public class Foo2 : Foo, IWhatever { }

public class Bar { }
public class Bar2 : Bar { }
public class Bar3 : Bar, IWhatever { }


public interface IModelProvider<T>
{
    U Create<U>() where U : T;
}

public class FooProvider : IModelProvider<Foo>
{
    public U Create<U>() where U : Foo
    {
        // create a proper "U" - for example Foo or Foo2
        return Activator.CreateInstance<U>(); // simpliest
    }
}

public class BarProvider : IModelProvider<Bar>
{
    public U Create<U>() where U : Bar
    {
        // create a proper "U" - for example Bar, Bar2 or Bar3
        // more verbose
        if (typeof(U) == typeof(Bar)) return (U)new Bar();
        if (typeof(U) == typeof(Bar2)) return (U)(object)new Bar2();
        if (typeof(U) == typeof(Bar3)) return (U)(object)new Bar3();

        throw new Exception();
    }
}

public class WhateverProvider : IModelProvider<IWhatever>
{
    public U Create<U>() where U : IWhatever, new()
    {
        // create a proper "U" - for example Foo2 or Bar3
        return new U(); // really the simpliest
    }
}

    public class VeryGenericProvider : IModelProvider { public TModel Create() where TModel : new() { return new TModel(); } }

public class ProviderFactory
{
    public static IModelProvider<T> Get<T>() where T : new()
    {
        // somehow choose a provider for T, dumb implementation just for example purposes
        if (typeof(T) == typeof(Foo)) return (IModelProvider<T>)new FooProvider();
        if (typeof(T) == typeof(Bar)) return (IModelProvider<T>)new BarProvider();
        if (typeof(T) == typeof(IWhatever)) return (IModelProvider<T>)new WhateverProvider();

        return VeryGenericProvider<T>();
    }
}

public static class ProviderTest
{
    public static void test()
    {
        Foo foo = ProviderFactory.Get<Foo>().Create<Foo>();
        Foo2 foo2 = ProviderFactory.Get<Foo>().Create<Foo2>();

        // Bar2 bar2 = ProviderFactory.Get<Foo>().Create<Bar2>(); - compile error
        Bar2 bar2 = ProviderFactory.Get<Bar>().Create<Bar2>(); // - ok!

        Bar3 bar3 = ProviderFactory.Get<IWhatever>().Create<Bar3>();
    }
}

このコードはコンパイルされますが、実行していません。「テスト」での最終的な使用法では型チェックが実行され、キャストは不要であることに注意してください。ただし、プロバイダーの内部実装では、いくつかのことをキャストする必要があります。たとえば、オブジェクトを手動で構築する BarProvider を参照してください。U が Bar2 であることが確実に「わかっている」場合でも、U の制約を Bar から Bar2 にアップグレードするように言語に指示する方法はありません。したがって、二重キャストが必要です。

私はこれを意味します:

return (U)new Bar(); // is ok because U is constrained to 'Bar'

return (U)new Bar2();         // will not compile, U:Bar is not related to Bar2:Bar
return (U)(object)new Bar2(); // is ok: temporary cast to object 'erases' type information
于 2013-05-09T09:26:07.680 に答える
1

もしかして

ModelBar bar = FooProvider.Get<ModelBar>().Create<ModelBar>();

というより

ModelBar bar = FooProvider.Get<ModelBar>().Create();

?

最初の ModelBar が実際には ModelBarBase または IModelBar であり、後者の ModelBar が実際には最終的な構築可能な型である場合を除き、前者は少し奇妙に感じます。

とにかく、あなたのコード例では Get() と Create() が同じクラス (Foo/FooBase) から来ているので、クラスのレイアウトも少し奇妙です。ここでSRPを台無しにしたと思います。

私はお勧めします:

public class FooProvider
{
    public static Provider<T> Get<T> { return new Provider<T>(); };
}

public class Provider<T>
{
    public T CreateDirect()
    {
        return Activator.Create<T>();
    }

    public TDerived CreateDerived<TDerived>() where TDerived : T
    {
        return Activator.Create<TDerived>();
    }
}

このセットアップでは、次のことができます。

class IMyInterf {}
class MyType : IMyInterf {}
class MyChildType : MyType {}

MyType tmp1 = FooProvider.Get<MyType>().CreateDirect();

MyChildType tmp1 = FooProvider.Get<MyType>().CreateDerived<MyChildType>();

MyChildType tmp1 = FooProvider.Get<IMyInterf>().CreateDerived<MyChildType>();

これは単なるスケッチであり、コンパイルせずに手書きで書いたものですが、一般的なアイデアを示しています。

「CreateDirect」を削除し、「CreateDerived」の名前を Create に変更すると、コードに似たものになりますが、より単純になります。そしてもちろん、本当に必要な場合は、これらの 2 つの個別のクラスを 1 つに混ぜてつぶすこともできますが、それには本当の意味がありません :)

編集:

もちろん、適切なプロバイダーの構築/検索を容易にするために、複数の andProvider<T>を導入することもできますが、実際のorを返す必要があります。ProviderBase<T>OrangeProvider:ProviderBase<Orange>Get<>ProviderBase<T>IProvider<T>

于 2013-05-09T08:54:58.077 に答える