web-dev-qa-db-ja.com

C#が静かなメソッドにインタフェイスの実装を許可しないのはなぜですか?

なぜC#はこのように設計されたのですか?

私が理解しているように、インターフェースは振る舞いを記述するだけで、そのインターフェースを実装するクラスが特定の振る舞いを実装するという契約上の義務を記述するという目的を果たします。

クラスがその振る舞いを共有メソッドで実装したい場合、なぜそうしないのですか?

これは私が考えていることの一例です。

// These items will be displayed in a list on the screen.
public interface IListItem {
  string ScreenName();
  ...
}

public class Animal: IListItem {
    // All animals will be called "Animal".
    public static string ScreenName() {
        return "Animal";
    }
....
}

public class Person: IListItem {

    private string name;

    // All persons will be called by their individual names.
    public string ScreenName() {
        return name;
    }

    ....

 }
431
Kramii

なぜあなたはこれができないのか尋ねているとします。

public interface IFoo {
    void Bar();
}

public class Foo: IFoo {
    public static void Bar() {}
}

これは意味的には意味がありません。インタフェースで指定されたメソッドは、オブジェクトと対話するためのコントラクトを指定するためにそこにあるべきです。静的メソッドでは、オブジェクトと対話することはできません。実装を静的にすることができる位置にいる場合は、そのメソッドが本当にインターフェースに属しているかどうかを確認する必要があります。


public class Animal: IListItem {
    /* Can be tough to come up with a different, yet meaningful name!
     * A different casing convention, like Java has, would help here.
     */
    public const string AnimalScreenName = "Animal";
    public string ScreenName(){ return AnimalScreenName; }
}

より複雑な状況では、常に別の静的メソッドを宣言してそれに委譲することができます。例を考えてみると、静的コンテキストとインスタンスコンテキストの両方で自明ではない何かをする理由は考えられませんでした。そのため、FooBar BLOBを省略して、それがその可能性を示唆しています。良い考えではありません。

211

私の(単純化された)技術的な理由は、静的メソッドが vtable になく、呼び出しサイトがコンパイル時に選択されることです。オーバーライドメンバーや仮想静的メンバーを持つことができないのも同じ理由です。詳細については、CS卒業生やコンパイラが必要です - 私はどちらでもありません。

政治的な理由で、私は Eric Lippert (誰かがコンパイラに勝ち、大学で数学、コンピューターサイエンス、応用数学の学士号を取得しています)を引用します。 Waterlooの(出典: LinkedIn ):

...静的メソッドの中心的な設計原則、それらに名前を付ける原則... [is] ...コンパイル時に、どのメソッドが呼び出されるかは常に正確に決定できます。つまり、このメソッドはコードの静的分析によってのみ解決できます。

Lippertはいわゆる型メソッドの余地を残しています。

つまり、(インスタンスや仮想とは異なり)nullを許容しない "this"引数を取らない(静的のような)型に関連付けられたメソッドですが、呼び出されるメソッドが構築されたTの型に依存します。静的とは異なり、コンパイル時に決定可能でなければなりません。

しかしまだその有用性を確信していません。

163
Mark Brackett

ここでのほとんどの答えは、その点をすべて見逃しているようです。多態性は、インスタンス間だけでなくタイプ間でも使用できます。私たちがジェネリックを使うとき、これはしばしば必要とされます。

ジェネリックメソッドにtypeパラメータがあり、それを使って何らかの操作を行う必要があるとします。私たちはコンストラクタに気づいていないので、私たちは即時化したくありません。

例えば:

Repository GetRepository<T>()
{
  //need to call T.IsQueryable, but can't!!!
  //need to call T.RowCount
  //need to call T.DoSomeStaticMath(int param)
}

...
var r = GetRepository<Customer>()

残念ながら、私は「醜い」代替案しか思いつかないでしょう。

  • リフレクションを使う醜いし、インターフェースやポリモーフィズムの概念を打ち負かします。

  • 完全に別のファクトリクラスを作成する

    これはコードの複雑さを大幅に増すかもしれません。たとえば、ドメインオブジェクトをモデル化しようとすると、各オブジェクトに別のリポジトリクラスが必要になります。

  • インスタンス化してから目的のインタフェースメソッドを呼び出します

    一般的なパラメータとして使用されているクラスのソースを制御しても、これを実装するのは困難です。その理由は、たとえば、インスタンスをよく知られた「DBに接続」状態にすることだけが必要な場合があるためです。

例:

public class Customer 
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  void SomeOtherMethod() 
  { 
    //do work...
  }
}

静的インターフェース問題を解決するために即時化を使用するには、以下のことを行う必要があります。

public class Customer: IDoSomeStaticMath
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  //dummy instance
  public Customer() { IsDummy = true; }

  int DoSomeStaticMath(int a) { }

  void SomeOtherMethod() 
  { 
    if(!IsDummy) 
    {
      //do work...
    }
  }
}

これは明らかに醜く、また不要で他のすべてのメソッドのコードを複雑にします。もちろん、エレガントな解決策でもありません。

88

私はそれが古い質問であることを知っていますが、それは面白いです。例は最高ではありません。私はあなたがユースケースを見せればそれがはるかに明確になると思います:

 string DoSomething <T>()ここで、T:ISomeFunction 
 {
 if(T.someFunction())
 ... 
} [] ]

静的メソッドを持てるだけ 実装する インターフェースはあなたが望むものを達成しないでしょう。次のように静的メンバーを持つことが必要になります。  インターフェース私は確かにそのための多くの使用例を想像することができます、特に物事を作成することができるようになるとき。私が提供できる2つのアプローチは役に立つかもしれません:

  1. 型パラメータが上記のDoSomethingに渡す型になる静的汎用クラスを作成します。このクラスの各バリエーションは、その型に関連するものを保持する1つ以上の静的メンバーを持ちます。この情報は、関心のある各クラスに「情報の登録」ルーチンを呼び出させるか、クラスバリエーションの静的コンストラクタが実行されたときにReflectionを使用して情報を取得することによって提供できます。私は後者のアプローチがComparer <T> .Default()のようなものによって使われていると思います。
  2. 対象となるクラスTごとに、IGetWhateverClassInfo <T>を実装し、「新しい」制約を満たすクラスまたは構造体を定義します。このクラスは実際にはフィールドを含みませんが、型情報を持つ静的フィールドを返す静的プロパティを持ちます。そのクラスまたは構造体の型を問題の一般的なルーチンに渡してください。これはインスタンスを作成し、それを使用して他のクラスに関する情報を取得することができます。この目的でクラスを使用する場合は、毎回新しい記述子オブジェクトのインスタンスを作成しなくても済むように、おそらく上記のように静的な総称クラスを定義する必要があります。構造体を使用する場合、インスタンス化コストはゼロにする必要がありますが、構造体の種類が異なればDoSomethingルーチンのさまざまな展開が必要になります。

これらのアプローチのどれも本当に魅力的ではありません。一方、CLRにこの種の機能を明確に提供するためのメカニズムがあれば、.netはパラメータ化された「新しい」制約を指定できるようになるでしょうそれが特定の署名を持つ静的メソッドを持っているかどうかを知ることの難しさにおいて同程度であるために)。

18
supercat

インターフェースが「コントラクト」を表すという点では、静的クラスがインターフェースを実装することは静かで妥当なようです。

上記の議論はすべて、契約に関するこの点を見逃しているようです。

14
George

近視眼、私は思います。

もともと設計されていたとき、インターフェースはクラスのインスタンスでのみ使用されることを意図していました

IMyInterface val = GetObjectImplementingIMyInterface();
val.SomeThingDefinedinInterface();

ジェネリックスに対する制約が静的メソッドをインターフェースに追加することは実用的な用途があるので、それはインターフェースの導入を伴うことのみでした。

(コメントへの回答:)今それを変更することはCLRへの変更を必要とするであろうと思います、そしてそれは既存のアセンブリとの不適合をもたらすでしょう。

14
James Curran

インターフェイスはオブジェクトの動作を指定します。

静的メソッドはオブジェクトの動作を指定しませんが、オブジェクトに何らかの影響を与える動作を指定します。

14
John Kraft

インターフェイスの目的は多態性を許可することであり、定義済みインターフェイスを実装するためにすべて定義された定義済みクラスをいくつでも渡すことができるということです...多態呼び出し内でコードが確実に見つけることができるようになります。呼び出しているメソッド静的メソッドにインターフェースを実装させるのは意味がありません。

どのようにあなたはそれを呼ぶでしょうか?


public interface MyInterface { void MyMethod(); }
public class MyClass: MyInterface
{
    public static void MyMethod() { //Do Something; }
}

 // inside of some other class ...  
 // How would you call the method on the interface ???
    MyClass.MyMethod();  // this calls the method normally 
                         // not through the interface...

    // This next fails you can't cast a classname to a different type... 
    // Only instances can be Cast to a different type...
    MyInterface myItf = MyClass as MyInterface;  
9
Charles Bretana

非ジェネリックコンテキストで使用される静的メソッドに関しては、インターフェースへの参照がある場合はそれらを呼び出すことができないので、インターフェースでそれらを許可することはあまり意味がないことに同意します。しかし、多態的な文脈ではなく一般的な文脈でインタフェースを使用することによって作成された言語設計には根本的な穴があります。この場合、インターフェースはまったくインターフェースではなく、むしろ制約です。 C#にはインターフェースの外にある制約の概念がないため、実質的な機能が欠けています。適例:

T SumElements<T>(T initVal, T[] values)
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

ここでは多態性は存在せず、ジェネリックはオブジェクトの実際の型を使用して+ =演算子を呼び出しますが、その演算子が存在することを確信できないためこれは失敗します。簡単な解決策はそれを制約の中で指定することです。演算子は静的であり、静的メソッドはインターフェース内には存在できず、(これが問題です)制約はインターフェースとして表されるため、単純な解決策は不可能です。

C#が必要とするものは実際の制約タイプです。すべてのインターフェースも制約になりますが、すべての制約がインターフェースになるわけではないので、次のようにします。

constraint CHasPlusEquals
{
    static CHasPlusEquals operator + (CHasPlusEquals a, CHasPlusEquals b);
}

T SumElements<T>(T initVal, T[] values) where T : CHasPlusEquals
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

すべての数値型に対してIArithmeticを実装するようにすることについてはすでに多くの話がありますが、制約は多態的な構成要素ではないので効率性が懸念され、CArithmetic制約を作成することで問題が解決します。

4
Jeremy Sorensen

あなたが望んでいるように見えるものは、Typeまたはその型の任意のインスタンスの両方を通して静的メソッドが呼ばれることを可能にするでしょう。これは少なくともあいまいさをもたらし、これは望ましい特性ではありません。

それが重要であるかどうか、これがベストプラクティスであるかどうか、また何らかの方法でパフォーマンスの問題があるかどうかについては、際限なく議論されるでしょう。単にそれをサポートしないことで、C#はそれを心配する必要がなくなります。

また、この要望に準拠したコンパイラーは、インスタンス・メソッドと静的メソッドの間のより厳密な分離を伴う可能性がある最適化を失う可能性があります。

3
AnthonyWJones

クラスの静的メソッドと非静的メソッドは異なるインタフェースであると考えることができます。呼び出されると、静的メソッドはシングルトン静的クラスオブジェクトに解決され、非静的メソッドは処理するクラスのインスタンスに解決されます。したがって、インターフェースで静的メソッドと非静的メソッドを使用する場合、実際には1つのまとまったものにアクセスするためにインターフェースを使用する必要がある場合は、2つのインターフェースを宣言することになります。

3
Scott Langham

インターフェイスメソッドの静的な実装、あるいはMark Brackettが "いわゆる型メソッド"として紹介したものが足りない例を示します。

データベースストレージから読み込むときは、任意の構造のテーブルから読み込むことを処理する汎用のDataTableクラスがあります。テーブル固有の情報はすべてテーブルごとに1つのクラスに入れられ、DBからの1行分のデータも保持され、IDataRowインターフェイスを実装する必要があります。 IDataRowには、データベースから読み込むテーブルの構造の説明が含まれています。 DataTableは、DBから読み取る前にIDataRowからデータ構造を要求する必要があります。現在これは次のようになります。

interface IDataRow {
  string GetDataSTructre();  // How to read data from the DB
  void Read(IDBDataRow);     // How to populate this datarow from DB data
}

public class DataTable<T> : List<T> where T : IDataRow {

  public string GetDataStructure()
    // Desired: Static or Type method:
    // return (T.GetDataStructure());
    // Required: Instantiate a new class:
    return (new T().GetDataStructure());
  }

}

GetDataStructureは、各テーブルを読み取るために1回だけ必要です。インスタンスをもう1つインスタンス化するためのオーバーヘッドは最小限です。しかし、この場合はここでいいでしょう。

3
Jesper Grooss

インターフェースは継承構造であり、静的メソッドはうまく継承されないからです。

3
Joel Coehoorn

ほとんどの人はOOPでクラスもオブジェクトであることを忘れているように見えます、そしてそれ故に彼らはメッセージを持っています。インスタンスオブジェクトとクラスオブジェクトの間に違いがあるという事実は、言語に欠陥または欠点を示すだけです。 C#について楽観的ですが….

1
Mar Bar

これは 'type method'が必要な例です。いくつかのソースXMLに基づいて一連のクラスの1つを作成しています。だから私は持っています

  static public bool IsHandled(XElement xml)

各クラスで順番に呼び出される関数。

そうでなければ不適切なオブジェクトを作成するのに時間がかかるので、関数は静的であるべきです。 @Ian Boyde氏が指摘しているように、ファクトリクラスで行うことも可能ですが、これは複雑さを増すだけです。

クラス実装者にそれを実装させるためにそれをインターフェースに追加するのはいいでしょう。これは大きなオーバーヘッドを引き起こさないでしょう - それはただコンパイル/リンク時のチェックであり、vtableに影響を及ぼしません。

しかし、それはまたかなりマイナーな改善になるでしょう。このメソッドは静的なので、呼び出し側としては明示的に呼び出す必要があります。実装されていない場合は即時コンパイルエラーが発生します。それをインターフェース上で指定できるようにすることは、このエラーが開発サイクルのほんの少し早い段階で起こることを意味しますが、これは他の壊れたインターフェースの問題と比べると簡単です。

それで、それは、バランスが取れているとおそらく最も省かれる可能性がある小さな潜在的な機能です。

1

インタフェースは定義された利用可能な機能の抽象的なセットです。

そのインターフェースのメソッドが静的に振る舞うかどうかは、インターフェースの背後に隠されるべき実装の詳細です。インターフェースメソッドを静的メソッドとして定義するのは間違っているでしょう。なぜならあなたは不必要にそのメソッドがある方法で実装されることを強いるからです。

メソッドが静的として定義されている場合、インタフェースを実装するクラスはそれほどカプセル化されていません。カプセル化は、オブジェクト指向設計で努力するのに適しています(なぜなら、ここでは説明しません。 http://en.wikipedia.org/wiki/Object-oriented )。 。このため、静的メソッドはインターフェイスでは許可されていません。

1
Scott Langham

静的クラスはこれを実行できるので、それらを総称的に使用できます。希望する結果を得るために、代わりにシングルトンを実装する必要がありました。

"User"、 "Team"などのエンティティタイプごとに "Create"、 "Read"、 "Update"、 "Delete"などのCRUDメソッドを実装したスタティックビジネスレイヤクラスがたくさんありました。それからベースを作成しました。 CRUDメソッドを実装したビジネスレイヤクラスの抽象プロパティを持つコントロール。これにより、基本クラスからの「作成」、「読み取り」、「更新」、「削除」操作を自動化できました。静的制限のため、シングルトンを使用しなければなりませんでした。

1
Louis Rebolloso

C#とCLRは、Javaと同じようにインターフェース内の静的メソッドをサポートする必要があります。静的修飾子は契約定義の一部であり、具体的には意味があります。具体的には、振る舞いと戻り値はインスタンスごとに異なるわけではありませんが、呼び出しごとに異なります。

そうは言っても、インターフェースで静的メソッドを使用したいができない場合は、代わりに注釈を使用することをお勧めします。あなたが探している機能を手に入れるでしょう。

1

FYI:あなたはインターフェースのための拡張メソッドを作成することによってあなたが望むものと同様の振る舞いを得ることができます。拡張メソッドは、共有された、オーバーライドできない静的な動作になります。ただし、残念ながら、この静的メソッドは契約の一部ではありません。

1
Daniel Auger

静的要素が含まれるクラスの特別なインスタンスを作成することによってMicrosoftが静的クラスをC#に実装しているという事実は、静的機能がどのように達成されるかの奇妙な点にすぎません。それは理論上の論点ではありません。

インターフェイスはクラスインターフェイスの記述子であるべきです - それがどのように相互作用するのか、そしてそれは静的な相互作用を含むべきです。インターフェースの一般的な定義(Meriam-Websterによる):さまざまなものが出会い、コミュニケーションを取り合う、または互いに影響を与える場所または領域。クラスの静的コンポーネントまたは静的クラスを完全に省略した場合、これらの悪い人たちがどのように相互作用するのかについての大部分のセクションは無視されます。

これは、静的クラスでインタフェースを使用できることが非常に有用である場合の非常に明確な例です。

public interface ICrudModel<T, Tk>
{
    Boolean Create(T obj);
    T Retrieve(Tk key);
    Boolean Update(T obj);
    Boolean Delete(T obj);
}

現在、私はこれらのメソッドを含む静的クラスを、私が何かを忘れていないことを確認するためのチェックなしで書いています。 OOP以前のプログラミングの昔のようなものです。

1
Thomas Phaneuf

簡単な答えは「有用性がゼロだから」だと思います。インターフェースメソッドを呼び出すには、その型のインスタンスが必要です。インスタンスメソッドから、必要な静的メソッドを呼び出すことができます。

0
mackenir

それをしないで、代わりに抽象クラスを試してください

public abstract class ExampleBase
{
    /// <summary>
    /// Do it
    /// </summary>
    public virtual abstract static void DoIt();
}
0
Eric Silveira

概念的にインターフェースが静的メソッドを含む規約を定義できない理由はありません。

現在のC#言語の実装では、この制限は基本クラスとインタフェースの継承を許可しているためです。 「クラスSomeBaseClass」が「インターフェースISomeInterface」および「クラスSomeDerivedClass:SomeBaseClass、ISomeInterface」もインターフェースを実装する場合、静的メソッドはインスタンスメソッドと同じシグネチャを持つことができないため、静的メソッドはコンパイルに失敗しますインタフェースを実装するために基本クラスに存在すること).

静的クラスは機能的にシングルトンと同一であり、より明確な構文を持つシングルトンと同じ目的を果たします。シングルトンはインターフェースを実装できるので、静的によるインターフェース実装は概念的に有効です。

そのため、インスタンス間でのC#の名前の競合と、継承をまたがった同じ名前の静的メソッドの制限になります。 C#が静的メソッド規約(インタフェース)をサポートするように「アップグレード」できなかった理由はありません。

0
Greg McPherran

クラスがインタフェースを実装するとき、それはインタフェースメンバのインスタンスを作成しています。静的型にはインスタンスがありませんが、インターフェース内に静的シグニチャーを持つ意味はありません。

0
Vinay Chanumolu

オブジェクト指向の概念に従ってインターフェースはクラスによって実装され、オブジェクトを使用してこれらの実装された関数(またはメソッド)にアクセスすることを契約しています。

そのため、Interface Contractメソッドにアクセスしたい場合は、オブジェクトを作成する必要があります。静的メソッドの場合は、絶対に許可されていません。静的クラス、メソッド、および変数はオブジェクトを必要とせず、その領域(またはクラス)のオブジェクトを作成せずにメモリにロードするか、またはオブジェクト作成を必要としないと言えます。

0

私は、C#がまさにこのような状況のために別のキーワードを必要とするという事実にたどり着いていると思います。戻り値が呼び出される型にのみ依存するメソッドが必要です。このタイプが不明の場合は、「静的」と呼ぶことはできません。しかし、型が判明すると、静的になります。 「未解決の静的」という考えです - それはまだ静的ではありませんが、受信タイプがわかればそうなるでしょう。これは完全に良い概念であり、プログラマがそれを求め続ける理由です。しかし、それはデザイナーが言語について考えた方法にはあまり合いませんでした。

それは利用できないので、私は以下に示す方法で非静的メソッドを使うことにしました。厳密には理想的ではありませんが、少なくとも私にとっては意味がありません。

public interface IZeroWrapper<TNumber> {
  TNumber Zero {get;}
}

public class DoubleWrapper: IZeroWrapper<double> {
  public double Zero { get { return 0; } }
}
0