C# では静的メソッドによるインターフェイスの実装が許可されないのはなぜですか?
-
06-07-2019 - |
質問
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;
}
....
}
解決
これを行うことができない理由を尋ねていると仮定すると:
public interface IFoo {
void Bar();
}
public class Foo: IFoo {
public static void Bar() {}
}
これは意味的には意味がありません。インターフェイスで指定されたメソッドは、オブジェクトと対話するためのコントラクトを指定するために存在する必要があります。静的メソッドでは、オブジェクトと対話することはできません。実装を静的にできる位置にいる場合は、そのメソッドが本当にインターフェイスに属しているかどうかを自問する必要があります。
あなたの例を実装するには、Animalにconstプロパティを与えます。これにより、静的コンテキストからアクセスできるようになり、その値を実装で返します。
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; }
}
より複雑な状況では、常に別の静的メソッドを宣言し、それに委任できます。例を考えてみると、静的コンテキストとインスタンスコンテキストの両方で自明ではない何かをする理由を考えることができませんでした。良い考えではありません。
他のヒント
私の(簡略化された)技術的な理由は、静的メソッドが vtable にないことです。呼び出しサイトはコンパイル時に選択されます。これは、オーバーライドまたは仮想静的メンバーを使用できないのと同じ理由です。詳細については、CS卒業生またはコンパイラーが必要です-私はどちらでもありません。
政治的な理由から、引用者Eric Lippert (編集者であり、ウォータールー大学で数学、コンピューターサイエンス、応用数学の学士号を取得しています(出典:< a href = "https://www.linkedin.com/pub/eric-lippert/85/934/a38" rel = "noreferrer"> LinkedIn ):
...静的メソッドの中心的な設計原則、名前を付ける原則... [is] ...これは、コンパイル時に、どのメソッドが呼び出されるかを常に正確に決定できます。つまり、メソッドはコードの静的分析によってのみ解決できます。
リッパートは、いわゆる型メソッドの余地を残していることに注意してください:
つまり、型(静的など)に関連付けられたメソッドで、null不可の<!>#8220; this <!>#8221;引数(インスタンスまたは仮想とは異なります)が、呼び出されるメソッドが構築されたT型に依存する引数(コンパイルとは異なり静的である必要はありません)。
しかし、その有用性はまだ確信されていません。
ここでの答えのほとんどは、ポイント全体を見逃しているようです。ポリモーフィズムは、インスタンス間だけでなく、タイプ間でも使用できます。ジェネリックを使用する場合、これはしばしば必要です。
ジェネリックメソッドに型パラメーターがあり、それを使用して何らかの操作を行う必要があるとします。コンストラクターを認識していないため、インスタンス化する必要はありません。
例:
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>()
残念なことに、私は<!> quot; ugly <!> quot;しか思いつきません。代替案:
-
リフレクションを使用 Uいし、インターフェイスとポリモーフィズムのアイデアを打ち負かします。
-
完全に独立したファクトリクラスを作成
これにより、コードの複雑さが大幅に増加する可能性があります。たとえば、ドメインオブジェクトをモデル化しようとしている場合、各オブジェクトには別のリポジトリクラスが必要になります。
-
インスタンス化して、目的のインターフェースメソッドを呼び出します
汎用パラメーターとして使用されるクラスのソースを制御する場合でも、これを実装するのは困難です。その理由は、たとえば、インスタンスが既知の<!> quot; DB <!> quotにのみ接続されている必要がある場合があるためです。状態。
例:
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...
}
}
}
これは明らかにいだけでなく、他のすべてのメソッドのコードを不必要に複雑にします。もちろん、エレガントなソリューションでもありません!
これは古い質問ですが、興味深いことです。この例は最適ではありません。使用例を示した方がはるかに明確になると思います:
string DoSomething<T>() where T:ISomeFunction { if (T.someFunction()) ... }
静的メソッドを持っているだけでは、実装ではインターフェースが望み通りになりません。必要なのは、インターフェイスの part として静的メンバーを持つことです。特に物を作ることができるようになると、そのための多くの使用例が確かに想像できます。役立つと思われる2つのアプローチ:
- 静的なジェネリッククラスを作成し、その型パラメーターは、上記のDoSomethingに渡す型になります。このクラスの各バリエーションには、そのタイプに関連するものを保持する1つ以上の静的メンバーがあります。この情報は、関心のある各クラスに<!> quot; register information <!> quot;ルーチン、またはReflectionを使用して、クラスバリエーションの静的コンストラクターの実行時に情報を取得します。後者のアプローチはComparer <!> lt; T <!> gt; .Default()のようなものによって使用されると思います。
- 対象の各クラスTについて、IGetWhateverClassInfo <!> lt; T <!> gt;を実装するクラスまたは構造体を定義します。 <!> quot; new <!> quot;を満たす制約。クラスには実際にはフィールドは含まれませんが、型情報を持つ静的フィールドを返す静的プロパティがあります。そのクラスまたは構造体の型を問題のジェネリックルーチンに渡します。これにより、インスタンスを作成し、それを使用して他のクラスに関する情報を取得できます。この目的でクラスを使用する場合、おそらく新しい記述子オブジェクトインスタンスを毎回構築する必要を避けるために、おそらく上記のように静的ジェネリッククラスを定義する必要があります。構造体を使用する場合、インスタンス化のコストはnilである必要がありますが、構造体のタイプごとにDoSomethingルーチンの異なる拡張が必要になります。
これらのアプローチはどれも本当に魅力的ではありません。一方、この種の機能をきれいに提供するメカニズムがCLRに存在する場合、.netではパラメーター化された<!> quot; new <!> quotを指定できると期待されます。制約(クラスに特定の署名を持つコンストラクターがあるかどうかを知ることは、特定の署名を持つ静的メソッドがあるかどうかを知ることと比較して難易度が高いようです)
インターフェイスはオブジェクトの動作を指定します。
静的メソッドはオブジェクトの動作を指定しませんが、何らかの方法でオブジェクトに影響する動作を指定します。
インターフェースが<!> quot; contracts <!> quot;を表す限り、静的クラスがインターフェースを実装することは静かに合理的です。
上記の議論はすべて、契約に関するこの点を見逃しているようです。
近視、私は推測します。
最初に設計されたとき、インターフェイスはクラスのインスタンスでのみ使用されることを意図していました
IMyInterface val = GetObjectImplementingIMyInterface();
val.SomeThingDefinedinInterface();
ジェネリックの制約がインターフェースに静的メソッドを追加することが実用的であったため、インターフェースの導入のみでした。
(コメントへの応答:)これを変更するには、CLRを変更する必要があり、既存のアセンブリとの非互換性につながると考えています。
インターフェイスの目的はポリモーフィズムを許可することであるため、定義されたインターフェイスを実装するためにすべて定義された任意の数の定義済みクラスのインスタンスを渡すことができます...ポリモーフィック呼び出し内でコードが呼び出しているメソッドを見つけることができます。静的メソッドがインターフェースを実装できるようにすることは意味がありません。
どのように呼びますか??
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;
非ジェネリックコンテキストで使用される静的メソッドについては、インターフェイスへの参照がある場合は呼び出すことができないため、インターフェイスで許可することはあまり意味がないことに同意します。ただし、ポリモーフィックなコンテキストではなく、汎用的なコンテキストでインターフェイスを使用することで作成される言語設計に基本的な穴があります。この場合、インターフェイスはまったくインターフェイスではなく、制約です。 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制約を作成するとその問題が解決されるため、効率が懸念されます。
インターフェースは継承構造にあり、静的メソッドはうまく継承されないため。
必要と思われるものは、Typeまたはその型のインスタンスの両方を介して静的メソッドを呼び出すことを可能にします。これにより、少なくとも曖昧さが生じ、望ましくない特性になります。
それが重要かどうか、それはベストプラクティスであり、それを何らかの方法で実行するパフォーマンスの問題があるかどうかについては、無限の議論があります。 C#をサポートしないだけで、C#を心配する必要がなくなります。
この欲求に準拠したコンパイラーは、インスタンスメソッドと静的メソッドのより厳密な分離を伴う可能性のある最適化を失う可能性もあります。
クラスの静的メソッドと非静的メソッドは、異なるインターフェースであると考えることができます。呼び出されると、静的メソッドはシングルトン静的クラスオブジェクトに解決され、非静的メソッドは処理するクラスのインスタンスに解決されます。そのため、インターフェイスで静的メソッドと非静的メソッドを使用する場合、1つのまとまりのあるものにアクセスするためにインターフェイスを実際に使用する場合、2つのインターフェイスを効果的に宣言することになります。
インターフェイスメソッドの静的実装、またはMark Brackettが<!> quot;いわゆるtype method <!> quot;:
として導入したもののいずれかが欠けている例を示します。データベースストレージから読み取る場合、任意の構造のテーブルからの読み取りを処理する汎用DataTableクラスがあります。すべてのテーブル固有の情報は、DBからの1行のデータも保持するテーブルごとに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つのインスタンスをインスタンス化するためのオーバーヘッドは最小限です。ただし、この場合はこちらがいいでしょう。
FYI:インターフェイスの拡張メソッドを作成することで、希望する動作に似た動作を得ることができます。拡張メソッドは、共有され、オーバーライドできない静的な動作になります。ただし、残念ながら、この静的メソッドはコントラクトの一部ではありません。
インターフェースは、定義された利用可能な機能の抽象的なセットです。
そのインターフェースのメソッドが静的として動作するかどうかは、インターフェースの背後に隠されるべき実装の詳細です。インターフェイスメソッドを静的として定義するのは間違っています。なぜなら、メソッドを特定の方法で不必要に強制するからです。
メソッドが静的として定義されている場合、インターフェイスを実装するクラスは、可能な限りカプセル化されません。カプセル化はオブジェクト指向設計で努力するのに良いことです(理由は説明しませんが、ここで読むことができます: http://en.wikipedia.org/wiki/Object-oriented )。このため、静的メソッドはインターフェイスで許可されていません。
静的クラスは、汎用的に使用できるようにこれを実行できる必要があります。代わりにシングルトンを実装して、目的の結果を達成する必要がありました。
<!> quot; Create <!> quot;、<!> quot; Read <!> quot;、<!> quot; Update <!>などのCRUDメソッドを実装した静的ビジネスレイヤークラスがたくさんありましたquot;、<!> quot; Delete <!> quot; <!> quot; User <!> quot;、<!> quot; Team <!> quot;などの各エンティティタイプに対して、実装したビジネスレイヤクラスの抽象プロパティを持つベースコントロールを作成しましたCRUDメソッド。これにより、<!> quot; Create <!> quot;、<!> quot; Read <!> quot;、<!> quot; Update <!> quot;、<!> quot; Delete <! > quot;基本クラスからの操作。静的な制限のため、シングルトンを使用する必要がありました。
ほとんどの人は、OOPクラスはオブジェクトでもあることを忘れているようです。 インスタンスオブジェクトとクラスオブジェクトの間に違いがあるという事実は、言語の欠陥または欠点のみを示しています。 C#については楽観的ですが...
OKは、「型メソッド」が必要な例です。ソースXMLに基づいて一連のクラスの1つを作成しています。だから私は持っています
static public bool IsHandled(XElement xml)
各クラスで順番に呼び出される関数。
この関数は静的でなければなりません。そうでなければ、不適切なオブジェクトの作成に時間を浪費します。 @Ian Boydeが指摘するように、それはファクトリクラスで行うことができますが、これは複雑さを追加するだけです。
インターフェースに追加して、クラスの実装者に強制的に実装させるとよいでしょう。これは大きなオーバーヘッドを引き起こすことはありません-コンパイル/リンク時間のチェックにすぎず、vtableには影響しません。
しかし、それはかなり小さな改善でもあります。メソッドは静的であるため、呼び出し元の私は明示的に呼び出す必要があり、実装されていない場合はすぐにコンパイルエラーが発生します。インターフェイスで指定できるようにすると、このエラーは開発サイクルのわずかに早い段階で発生しますが、これは他の壊れたインターフェイスの問題と比べると些細なことです。
つまり、これはおそらく潜在的に除外するのが最適なマイナーな機能です。
静的なクラスが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以前のプログラミングの悪い昔のようです。
C#およびCLRは、Javaと同様に、インターフェイスで静的メソッドをサポートする必要があります。 static修飾子はコントラクト定義の一部であり、具体的には意味があります。具体的には、動作と戻り値はインスタンスごとに変化しませんが、呼び出しごとに変化する可能性があります。
とはいえ、インターフェイスで静的メソッドを使用したいが使用できない場合は、代わりに注釈を使用することをお勧めします。探している機能が得られます。
短い答えは<!> quot;有用性がゼロだからだと思います<!> quot;。 インターフェイスメソッドを呼び出すには、そのタイプのインスタンスが必要です。インスタンスメソッドから、必要な静的メソッドを呼び出すことができます。
質問は、まさにこの種の状況のために、C# には別のキーワードが必要であるという事実に焦点を当てていると思います。戻り値が呼び出された型のみに依存するメソッドが必要です。型が不明な場合、それを「静的」と呼ぶことはできません。しかし、型が判明すると、それは静的になります。「未解決の静的」というのがアイデアです。まだ静的ではありませんが、受信タイプがわかれば静的になります。これは非常に優れた概念であるため、プログラマーはそれを求め続けます。しかし、それはデザイナーの言語に対する考え方とは完全に一致しませんでした。
それが利用できないため、以下に示す方法で非静的メソッドを使用することにしました。必ずしも理想的というわけではありませんが、少なくとも私にとっては、これ以上に理にかなったアプローチは見つかりません。
public interface IZeroWrapper<TNumber> {
TNumber Zero {get;}
}
public class DoubleWrapper: IZeroWrapper<double> {
public double Zero { get { return 0; } }
}
クラスおよび これらの実装された機能(またはメソッド)にアクセスする契約を持っています オブジェクト。
したがって、インターフェイスコントラクトメソッドにアクセスする場合は、オブジェクトを作成する必要があります。静的メソッドの場合は許可されないことが常に必要です。静的クラス、メソッド、および変数はオブジェクトを必要とせず、その領域(またはクラス)のオブジェクトを作成せずにメモリにロードするか、オブジェクトの作成を必要としないと言うことができます。
概念的に インターフェースが静的メソッドを含むコントラクトを定義できなかった理由はありません。
現在のC#言語の実装では、制限は基本クラスとインターフェイスの継承の許可によるものです。 If <!> quot; class SomeBaseClass <!> quot; <!> quot; interface ISomeInterface <!> quot;を実装します。および<!> quot; class SomeDerivedClass:SomeBaseClass、ISomeInterface <!> quot;また、インターフェイスを実装します。インターフェイスメソッドを実装するための静的メソッドは、インスタンスメソッド(インターフェイスを実装するための基本クラスに存在する)と同じシグネチャを持つことができないため、コンパイルに失敗します。
静的クラスは、機能的にシングルトンと同一であり、シンタックスがクリーンなシングルトンと同じ目的を果たします。シングルトンはインターフェースを実装できるため、静的によるインターフェースの実装は概念的に有効です。
つまり、継承全体で同じ名前のインスタンスと静的メソッドのC#名の競合の制限に要約されます。 C#を<!> quot; upgraded <!> quotにできなかった理由はありません。静的メソッドコントラクト(インターフェイス)をサポートします。
それをしないで、代わりに抽象クラスを試してください
public abstract class ExampleBase
{
/// <summary>
/// Do it
/// </summary>
public virtual abstract static void DoIt();
}
クラスがインターフェイスを実装すると、インターフェイスメンバのインスタンスが作成されます。静的な型にはインスタンスはありませんが、インターフェイスに静的な署名を持つ意味はありません。