web-dev-qa-db-ja.com

コンストラクタシグネチャを定義するインタフェース?

私がこの問題にぶつかったのは今回が初めてだというのは変です。

C#インターフェイスでコンストラクタをどのように定義しますか?

編集
例を望んでいる人もいました(これは空き時間のプロジェクトなので、そうです、ゲームです)

IDrawable
+更新
+描く

更新(画面の端などをチェック)して描画できるようにするには、常にGraphicsDeviceManagerが必要になります。だから私はオブジェクトがそれへの参照を持っていることを確認したいです。これはコンストラクタに属します。

これを書き留めたので、ここで実装しているのはIObservableであり、GraphicsDeviceManagerIDrawable...を取るべきであると思います。私はXNAフレームワークを入手できないか、フレームワークがあまりよく考えられていないようです。

編集
インターフェースという文脈では、私のコンストラクタの定義について混乱があるようです。インタフェースは実際にはインスタンス化できないので、コンストラクタは必要ありません。私が定義したいのはコンストラクタへの署名でした。インタフェースが特定のメソッドのシグネチャを定義できるのとまったく同じように、インタフェースはコンストラクタのシグネチャを定義できます。

498
Boris Callens

すでによく知られているように、あなたはインターフェース上にコンストラクタを持つことはできません。しかし、これは7年後のグーグルでの非常にランク付けされた結果であるので、私はここでチップインすると思いました同様の状況のた​​めに将来必要です。この概念はすでにいくつかのコメントで示唆されていますが、実際にそれを実行する方法を示す価値があると思いました。

これまでのところ、これまでのようなメインのインターフェースがあります。

public interface IDrawable
{
    void Update();
    void Draw();
}

それでは、強制したいコンストラクタを使って抽象クラスを作成しましょう。実際には、あなたが最初の質問を書いたときから利用可能になっているので、ここで少しおしゃれにしてこの状況で総称を使うことができます

public abstract class MustInitialize<T>
{
    public MustInitialize(T parameters)
    {

    }
}

今度は、IDrawableインターフェースとMustInitialize抽象クラスの両方から継承する新しいクラスを作成する必要があります。

public class Drawable : MustInitialize<GraphicsDeviceManager>, IDrawable
{
    GraphicsDeviceManager _graphicsDeviceManager;

    public Drawable(GraphicsDeviceManager graphicsDeviceManager)
        : base (graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }

    public void Update()
    {
        //use _graphicsDeviceManager here to do whatever
    }

    public void Draw()
    {
        //use _graphicsDeviceManager here to do whatever
    }
}

それからDrawableのインスタンスを作成するだけでいいのです。

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

ここで素晴らしいことは、作成した新しいDrawableクラスがまだIDrawableに期待されるものとまったく同じように動作することです。

MustInitializeコンストラクタに複数のパラメータを渡す必要がある場合は、渡す必要があるすべてのフィールドのプロパティを定義するクラスを作成できます。

110
Dan

できません。それは時々痛みですが、とにかく普通のテクニックを使ってそれを呼ぶことができないでしょう。

ブログの投稿で、私は 静的インタフェース を提案しました。これは一般的な型制約でしか使えないでしょう - しかしIMOは本当に便利かもしれません。

がインターフェース内でコンストラクタを定義できる場合、クラスを派生させるのに問題があるでしょう。

public class Foo : IParameterlessConstructor
{
    public Foo() // As per the interface
    {
    }
}

public class Bar : Foo
{
    // Yikes! We now don't have a parameterless constructor...
    public Bar(int x)
    {
    }
}
308
Jon Skeet

非常に最近の寄稿で、インターフェースコンストラクタに関する別の問題が示されました。 (私はこの質問を選びました。なぜなら、それは問題の明確な表現を持っているからです)。我々が持っていると仮定します。

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

class Person : IPerson, ICustomer
{
    Person(string name) { }
    Person(DateTime registrationDate) { }
}

慣例により、「インタフェースコンストラクタ」の実装は型名に置き換えられます。

今インスタンスを作ります:

ICustomer a = new Person("Ernie");

契約ICustomerは遵守されていると言えますか?

そしてこれはどうですか?

interface ICustomer
{
    ICustomer(string address);
}
133
Gert Arnold

できません。

インタフェースは、他のオブジェクトが実装するコントラクトを定義しているため、初期化する必要がある状態はありません。

初期化が必要な状態がある場合は、代わりに抽象基本クラスを使用することを検討してください。

61
Michael

コンストラクターを定義するインターフェースを作成することはできませんが、型にパラメーターなしのコンストラクターを強制するインターフェースを定義することは可能ですisそれはジェネリックを使用する非常にい構文です...実際、それが本当に良いコーディングパターンであるかどうかはわかりません。

public interface IFoo<T> where T : new()
{
  void SomeMethod();
}

public class Foo : IFoo<Foo>
{
  // This will not compile
  public Foo(int x)
  {

  }

  #region ITest<Test> Members

  public void SomeMethod()
  {
    throw new NotImplementedException();
  }

  #endregion
}

一方、型にパラメーターなしのコンストラクターがあるかどうかをテストする場合は、リフレクションを使用してそれを行うことができます。

public static class TypeHelper
{
  public static bool HasParameterlessConstructor(Object o)
  {
    return HasParameterlessConstructor(o.GetType());
  }

  public static bool HasParameterlessConstructor(Type t)
  {
    // Usage: HasParameterlessConstructor(typeof(SomeType))
    return t.GetConstructor(new Type[0]) != null;
  }
}

お役に立てれば。

20
Jeroen Landheer

私はこの質問を振り返っていました、そして私は自分自身に考えました、多分我々はこの問題を間違った方法で近づけています。特定のパラメータを使ってコンストラクタを定義することに関しては、インタフェースはうまくいかないかもしれませんが...(抽象)基本クラスはそうです。

必要なパラメーターを受け入れるコンストラクターをその上に持つ基本クラスを作成する場合、それから派生するすべてのクラスはそれらを提供する必要があります。

public abstract class Foo
{
  protected Foo(SomeParameter x)
  {
    this.X = x;
  }

  public SomeParameter X { get; private set }
}

public class Bar : Foo // Bar inherits from Foo
{
  public Bar() 
    : base(new SomeParameter("etc...")) // Bar will need to supply the constructor param
  {
  }
}
19
Jeroen Landheer

私が見つけたこの問題を解決する一つの方法は別の工場に建設を分けることです。たとえば、IQueueItemという抽象クラスがあり、そのオブジェクトを別のオブジェクトとの間で変換する方法が必要です(CloudQueueMessage)。だから私は持っているインターフェイスIQueueItemに -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

さて、実際のキュークラスでCloudQueueMessageをIQueueItemに変換し直す方法も必要です。つまり、IQueueItem objMessage = ItemType.FromMessageのような静的構造が必要です。代わりに私は別のインターフェースIQueueFactoryを定義しました -

public interface IQueueItemFactory<T> where T : IQueueItem
{
    T FromMessage(CloudQueueMessage objMessage);
}

これで、私の場合は主な問題であったnew()制約なしで、ようやく汎用キュークラスを書くことができます。

public class AzureQueue<T> where T : IQueueItem
{
    private IQueueItemFactory<T> _objFactory;
    public AzureQueue(IQueueItemFactory<T> objItemFactory)
    {
        _objFactory = objItemFactory;
    }


    public T GetNextItem(TimeSpan tsLease)
    {
        CloudQueueMessage objQueueMessage = _objQueue.GetMessage(tsLease);
        T objItem = _objFactory.FromMessage(objQueueMessage);
        return objItem;
    }
}

今私は私のための基準を満たすインスタンスを作成することができます

 AzureQueue<Job> objJobQueue = new JobQueue(new JobItemFactory())

うまくいけば、これはいつか他の誰かを助ける、明らかに問題と解決策を見せるために削除された内部コードの多く

6
JTtheGeek

一般的な工場アプローチはまだ理想的です。ファクトリがパラメータを必要としていることを知っているでしょう、そしてそれらのパラメータがインスタンス化されているオブジェクトのコンストラクタに渡されるのはまさにそのように起こるでしょう。

注意、これは単なる構文検証済みの疑似コードです。実行時に警告が表示される場合があります。

public interface IDrawableFactory
{
    TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
              where TDrawable: class, IDrawable, new();
}

public class DrawableFactory : IDrawableFactory
{
    public TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
                     where TDrawable : class, IDrawable, new()
    {
        return (TDrawable) Activator
                .CreateInstance(typeof(TDrawable), 
                                graphicsDeviceManager);
    }

}

public class Draw : IDrawable
{
 //stub
}

public class Update : IDrawable {
    private readonly GraphicsDeviceManager _graphicsDeviceManager;

    public Update() { throw new NotImplementedException(); }

    public Update(GraphicsDeviceManager graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }
}

public interface IDrawable
{
    //stub
}
public class GraphicsDeviceManager
{
    //stub
}

考えられる使用例

    public void DoSomething()
    {
        var myUpdateObject = GetDrawingObject<Update>(new GraphicsDeviceManager());
        var myDrawObject = GetDrawingObject<Draw>(null);
    }

確かに、あなたは常に適切な初期化されたオブジェクトを持っていることを保証するためにファクトリを通してcreateインスタンスだけが欲しいです。おそらく、 AutoFac のような依存性注入フレームワークを使うことは意味があります。 Update()はIoCコンテナに新しいGraphicsDeviceManagerオブジェクトを要求する可能性があります。

5
Matthew

この問題を解決する1つの方法は、総称とnew()制約を利用することです。

コンストラクタをメソッド/関数として表現する代わりに、ファクトリクラス/インタフェースとして表現することができます。クラスのオブジェクトを作成する必要があるすべての呼び出しサイトでnew()総称制約を指定すると、それに応じてコンストラクター引数を渡すことができます。

あなたのIDrawableの例では:

public interface IDrawable
{
    void Update();
    void Draw();
}

public interface IDrawableConstructor<T> where T : IDrawable
{
    T Construct(GraphicsDeviceManager manager);
}


public class Triangle : IDrawable
{
    public GraphicsDeviceManager Manager { get; set; }
    public void Draw() { ... }
    public void Update() { ... }
    public Triangle(GraphicsDeviceManager manager)
    {
        Manager = manager;
    }
}


public TriangleConstructor : IDrawableConstructor<Triangle>
{
    public Triangle Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 
}

今、あなたがそれを使うとき:

public void SomeMethod<TBuilder>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<Triangle>, new()
{
    // If we need to create a triangle
    Triangle triangle = new TBuilder().Construct(manager);

    // Do whatever with triangle
}

明示的なインターフェイス実装を使用して、すべての作成メソッドを単一のクラスに集中させることもできます。

public DrawableConstructor : IDrawableConstructor<Triangle>,
                             IDrawableConstructor<Square>,
                             IDrawableConstructor<Circle>
{
    Triangle IDrawableConstructor<Triangle>.Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 

    Square IDrawableConstructor<Square>.Construct(GraphicsDeviceManager manager)
    {
        return new Square(manager);
    } 

    Circle IDrawableConstructor<Circle>.Construct(GraphicsDeviceManager manager)
    {
        return new Circle(manager);
    } 
}

使用するには:

public void SomeMethod<TBuilder, TShape>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<TShape>, new()
{
    // If we need to create an arbitrary shape
    TShape shape = new TBuilder().Construct(manager);

    // Do whatever with the shape
}

もう1つの方法は、初期化子としてラムダ式を使用することです。呼び出し階層の早い段階で、どのオブジェクトをインスタンス化する必要があるのか​​がわかります(つまり、GraphicsDeviceManagerオブジェクトを作成または参照しているとき)。持っているとすぐに、ラムダを渡す

() => new Triangle(manager) 

それ以降の方法では、それ以降はどのようにしてTriangleを作成するかがわかります。必要なメソッドをすべて決定できない場合は、常にリフレクションを使用してIDrawableを実装する型の辞書を作成し、共有の場所に格納するか、またはに渡すことができる辞書に上記のラムダ式を登録するさらなる関数呼び出し.

3
Cesar

あなたはこれをジェネリックストリックで行うことができますが、それでもJon Skeetが書いたものに対して脆弱です:

public interface IHasDefaultConstructor<T> where T : IHasDefaultConstructor<T>, new()
{
}

このインタフェースを実装するクラスには、パラメータのないコンストラクタが必要です。

public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
    public A(int a) { } //compile time error
}
2
ghord

インタフェースの目的は、特定のオブジェクト署名を強制することです。オブジェクトが内部的にどのように機能するのかを明示的に考慮してはいけません。したがって、インターフェース内のコンストラクターは、概念的な観点からは意味がありません。

いくつかの選択肢がありますが:

  • 最小限のデフォルト実装として機能する抽象クラスを作成します。そのクラスには、実装クラスに必要なコンストラクタが必要です。

  • やり過ぎても構わない場合は、AbstractFactoryパターンを使用して、必要なシグネチャを持つファクトリクラスインタフェースでメソッドを宣言します。

  • GraphicsDeviceManagerUpdateおよびDrawメソッドにパラメータとして渡します。

  • GraphicsDeviceManagerをそれを必要とするオブジェクトの部分に渡すためにCompositional Object Oriented Programmingフレームワークを使います。これは私の考えではかなり実験的な解決策です。

あなたが説明する状況は一般的に扱うのが容易ではありません。同様のケースは、データベースへのアクセスを必要とするビジネスアプリケーション内のエンティティです。

2
MauganRa

インターフェースでコンストラクタを定義することが可能であれば、それは非常に役に立ちます。

インターフェースが指定された方法で使用されなければならないコントラクトであると仮定すると。以下のアプローチは、いくつかのシナリオでは実行可能な代替方法です。

public interface IFoo {

    /// <summary>
    /// Initialize foo.
    /// </summary>
    /// <remarks>
    /// Classes that implement this interface must invoke this method from
    /// each of their constructors.
    /// </remarks>
    /// <exception cref="InvalidOperationException">
    /// Thrown when instance has already been initialized.
    /// </exception>
    void Initialize(int a);

}

public class ConcreteFoo : IFoo {

    private bool _init = false;

    public int b;

    // Obviously in this case a default value could be used for the
    // constructor argument; using overloads for purpose of example

    public ConcreteFoo() {
        Initialize(42);
    }

    public ConcreteFoo(int a) {
        Initialize(a);
    }

    public void Initialize(int a) {
        if (_init)
            throw new InvalidOperationException();
        _init = true;

        b = a;
    }

}
0
Lea Hayes

私はそれを防弾にするために次のパターンを使います。

  • ベースから自分のクラスを派生する開発者は、パブリックにアクセス可能なコンストラクタを誤って作成することはできません
  • 最終的なクラス開発者は共通のcreateメソッドを通り抜けることを強いられます
  • すべてがタイプセーフで、キャスティングは不要です
  • これは100%柔軟で、どこでも再利用することができ、そこであなた自身の基本クラスを定義することができます。
  • 試してみてください(エラーフラグをtrueに設定せずに廃止フラグを定義した場合は例外ですが、それでも警告が表示される場合を除きます)

        public abstract class Base<TSelf, TParameter>
        where TSelf : Base<TSelf, TParameter>, new()
    {
        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TSelf Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);
    
            return me;
        }
    
        [Obsolete(FactoryMessage, true)]
        protected Base()
        {
        }
    
    
    
        protected virtual void Initialize(TParameter parameter)
        {
    
        }
    }
    
    public abstract class BaseWithConfig<TSelf, TConfig>: Base<TSelf, TConfig>
        where TSelf : BaseWithConfig<TSelf, TConfig>, new()
    {
        public TConfig Config { get; private set; }
    
        [Obsolete(FactoryMessage, true)]
        protected BaseWithConfig()
        {
        }
        protected override void Initialize(TConfig parameter)
        {
            this.Config = parameter;
        }
    }
    
    public class MyService : BaseWithConfig<MyService, (string UserName, string Password)>
    {
        [Obsolete(FactoryMessage, true)]
        public MyService()
        {
        }
    }
    
    public class Person : Base<Person, (string FirstName, string LastName)>
    {
        [Obsolete(FactoryMessage,true)]
        public Person()
        {
        }
    
        protected override void Initialize((string FirstName, string LastName) parameter)
        {
            this.FirstName = parameter.FirstName;
            this.LastName = parameter.LastName;
        }
    
        public string LastName { get; private set; }
    
        public string FirstName { get; private set; }
    }
    
    
    
    [Test]
    public void FactoryTest()
    {
        var notInitilaizedPerson = new Person(); // doesn't compile because of the obsolete attribute.
        Person max = Person.Create(("Max", "Mustermann"));
        Assert.AreEqual("Max",max.FirstName);
    
        var service = MyService.Create(("MyUser", "MyPassword"));
        Assert.AreEqual("MyUser", service.Config.UserName);
    }
    

EDIT:そして、これはあなたの描画例に基づいた例で、インターフェースの抽象化さえも強制します

        public abstract class BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithAbstraction<TSelf, TInterface, TParameter>, TInterface, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected BaseWithAbstraction()
        {
        }

        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TInterface Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);

            return me;
        }

        protected virtual void Initialize(TParameter parameter)
        {

        }
    }



    public abstract class BaseWithParameter<TSelf, TInterface, TParameter> : BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithParameter<TSelf, TInterface, TParameter>, TInterface, new()
    {
        protected TParameter Parameter { get; private set; }

        [Obsolete(FactoryMessage, true)]
        protected BaseWithParameter()
        {
        }
        protected sealed override void Initialize(TParameter parameter)
        {
            this.Parameter = parameter;
            this.OnAfterInitialize(parameter);
        }

        protected virtual void OnAfterInitialize(TParameter parameter)
        {
        }
    }


    public class GraphicsDeviceManager
    {

    }
    public interface IDrawable
    {
        void Update();
        void Draw();
    }

    internal abstract class Drawable<TSelf> : BaseWithParameter<TSelf, IDrawable, GraphicsDeviceManager>, IDrawable 
        where TSelf : Drawable<TSelf>, IDrawable, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected Drawable()
        {
        }

        public abstract void Update();
        public abstract void Draw();
    }

    internal class Rectangle : Drawable<Rectangle>
    {
        [Obsolete(FactoryMessage, true)]
        public Rectangle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }
    internal class Circle : Drawable<Circle>
    {
        [Obsolete(FactoryMessage, true)]
        public Circle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }


    [Test]
    public void FactoryTest()
    {
        // doesn't compile because interface abstraction is enforced.
        Rectangle rectangle = Rectangle.Create(new GraphicsDeviceManager());

        // you get only the IDrawable returned.
        IDrawable service = Circle.Create(new GraphicsDeviceManager());
    }
0
Tom

ある種のコンストラクターを強制する1つの方法は、インターフェースでGettersのみを宣言することです。これは、実装クラスに値を設定する(privately)ためのメソッド、理想的にはコンストラクターが必要であることを意味します。

0
Kunal

あなたは違います。

コンストラクターは、インターフェースを実装できるクラスの一部です。インタフェースはクラスが実装しなければならないメソッドの単なる契約です。

0
royatl

インターフェイスでコンストラクタシグネチャを定義することはできませんが、これが抽象クラスを検討するためのスポットになる可能性があることを言及する価値があります。抽象クラスは、インターフェイスと同じ方法で未実装の(抽象)メソッドシグネチャを定義できますが、(具象の)メソッドとコンストラクタを実装することもできます。

欠点は、それがクラスのタイプであるため、インターフェースが可能な多重継承タイプのシナリオには使用できないことです。

0
IrishLagger

インターフェイスはコントラクトですインターフェイスはフィールドを許可しません。つまり、初期化は必要ありません。インターフェイスは参照のみを保持します。インターフェイスは派生クラスがメモリを保持する必要があります。

0