C# Language
インターフェイス
サーチ…
インタフェースの実装
インタフェースは、それを「実装する」どのクラスにもメソッドの存在を強制するために使用されます。インターフェースはキーワードinterface
定義され、クラスはクラス名の後ろに: InterfaceName
を追加することでそれを '実装'できます。クラスは、各インタフェースをコンマで区切って複数のインタフェースを実装できます。
: InterfaceName, ISecondInterface
public interface INoiseMaker
{
string MakeNoise();
}
public class Cat : INoiseMaker
{
public string MakeNoise()
{
return "Nyan";
}
}
public class Dog : INoiseMaker
{
public string MakeNoise()
{
return "Woof";
}
}
彼らはINoiseMaker
を実装しているINoiseMaker
、 cat
とdog
両方にstring MakeNoise()
を含める必要があり、それなしではコンパイルに失敗します。
複数のインタフェースの実装
public interface IAnimal
{
string Name { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
public class Cat : IAnimal, INoiseMaker
{
public Cat()
{
Name = "Cat";
}
public string Name { get; set; }
public string MakeNoise()
{
return "Nyan";
}
}
明示的なインターフェイスの実装
明示的なインタフェースの実装は、共通のメソッドを定義する複数のインタフェースを実装する場合に必要ですが、メソッドを呼び出すために使用されるインタフェースに応じて異なる実装が必要です(複数のインタフェースが同じメソッドを共有する場合は明示的な実装は必要ありません。共通の実装が可能である)。
interface IChauffeur
{
string Drive();
}
interface IGolfPlayer
{
string Drive();
}
class GolfingChauffeur : IChauffeur, IGolfPlayer
{
public string Drive()
{
return "Vroom!";
}
string IGolfPlayer.Drive()
{
return "Took a swing...";
}
}
GolfingChauffeur obj = new GolfingChauffeur();
IChauffeur chauffeur = obj;
IGolfPlayer golfer = obj;
Console.WriteLine(obj.Drive()); // Vroom!
Console.WriteLine(chauffeur.Drive()); // Vroom!
Console.WriteLine(golfer.Drive()); // Took a swing...
実装は、インタフェースを使用する以外はどこからでも呼び出すことはできません。
public class Golfer : IGolfPlayer
{
string IGolfPlayer.Drive()
{
return "Swinging hard...";
}
public void Swing()
{
Drive(); // Compiler error: No such method
}
}
このため、明示的に実装されたインタフェースの複雑な実装コードを別個のプライベートメソッドに入れることは有益です。
もちろん、明示的なインタフェースの実装は、そのインタフェースに実際に存在するメソッドに対してのみ使用できます。
public class ProGolfer : IGolfPlayer
{
string IGolfPlayer.Swear() // Error
{
return "The ball is in the pit";
}
}
同様に、クラスのインタフェースを宣言せずに明示的なインタフェース実装を使用すると、エラーも発生します。
ヒント:
明示的にインタフェースを実装することで、デッドコードを回避することもできます。メソッドがもはや必要なくなり、インタフェースから取り除かれると、コンパイラはまだ存在する各実装について不満を持ちます。
注意:
プログラマーは、型のコンテキストにかかわらず契約が同じであることを期待しています。明示的な実装では、呼び出されたときに異なる動作を公開すべきではありません。上記の例とは異なり、 IGolfPlayer.Drive
とDrive
は可能な限り同じことを行う必要があります。
インターフェイスを使用する理由
インターフェースとは、インターフェースのユーザーとそれを実装するクラスとの間の契約の定義です。インタフェースを考える方法の1つは、オブジェクトが特定の機能を実行できるという宣言である。
異なるタイプのシェイプを表現するためのインタフェースIShape
を定義し、シェイプに領域があることを期待しているので、インタフェース実装がその領域を返すように強制するメソッドを定義します。
public interface IShape
{
double ComputeArea();
}
次の2つの図形があります: Rectangle
とCircle
public class Rectangle : IShape
{
private double length;
private double width;
public Rectangle(double length, double width)
{
this.length = length;
this.width = width;
}
public double ComputeArea()
{
return length * width;
}
}
public class Circle : IShape
{
private double radius;
public Circle(double radius)
{
this.radius = radius;
}
public double ComputeArea()
{
return Math.Pow(radius, 2.0) * Math.PI;
}
}
それらのそれぞれは、その領域の独自の定義を持っていますが、どちらも形状です。だから私たちのプログラムでは、それらをIShape
として見ることは論理的です。
private static void Main(string[] args)
{
var shapes = new List<IShape>() { new Rectangle(5, 10), new Circle(5) };
ComputeArea(shapes);
Console.ReadKey();
}
private static void ComputeArea(IEnumerable<IShape> shapes)
{
foreach (shape in shapes)
{
Console.WriteLine("Area: {0:N}, shape.ComputeArea());
}
}
// Output:
// Area : 50.00
// Area : 78.54
インタフェースの基礎
機能の「契約」として知られるインタフェースの機能。つまり、プロパティとメソッドを宣言しますが、実装されていません。
だからクラスのインターフェイスとは違って:
- インスタンス化できません
- 機能を持つことはできません
- メソッドのみを含めることができます* (プロパティとイベントは内部的にメソッドです)
- インタフェースを継承することを「実装する」といいます。
- あなたは1つのクラスから継承することができますが、複数のインターフェイスを実装することができます
public interface ICanDoThis{
void TheThingICanDo();
int SomeValueProperty { get; set; }
}
注意事項:
- "I"接頭辞はインタフェースに使用される命名規則です。
- 関数本体はセミコロン ";"に置き換えられます。
- 内部的にはメソッドでもあるため、プロパティも許可されています
public class MyClass : ICanDoThis {
public void TheThingICanDo(){
// do the thing
}
public int SomeValueProperty { get; set; }
public int SomeValueNotImplemtingAnything { get; set; }
}
。
ICanDoThis obj = new MyClass();
// ok
obj.TheThingICanDo();
// ok
obj.SomeValueProperty = 5;
// Error, this member doesn't exist in the interface
obj.SomeValueNotImplemtingAnything = 5;
// in order to access the property in the class you must "down cast" it
((MyClass)obj).SomeValueNotImplemtingAnything = 5; // ok
これは、WinFormsやWPFなどのUIフレームワークで作業する場合に特に便利です。これは、基本クラスから継承してユーザーコントロールを作成し、異なるコントロールタイプに対して抽象化を作成する能力が失われてしまうためです。例?来る:
public class MyTextBlock : TextBlock {
public void SetText(string str){
this.Text = str;
}
}
public class MyButton : Button {
public void SetText(string str){
this.Content = str;
}
}
提案された問題は、どちらも "テキスト"という概念を含んでいますが、プロパティ名が異なります。また、2つの異なるクラスに必須の継承があるため、 抽象基本クラスを作成することはできません。インターフェイスを使用すると、
public interface ITextControl{
void SetText(string str);
}
public class MyTextBlock : TextBlock, ITextControl {
public void SetText(string str){
this.Text = str;
}
}
public class MyButton : Button, ITextControl {
public void SetText(string str){
this.Content = str;
}
public int Clicks { get; set; }
}
今すぐMyButtonとMyTextBlockは互換性があります。
var controls = new List<ITextControls>{
new MyTextBlock(),
new MyButton()
};
foreach(var ctrl in controls){
ctrl.SetText("This text will be applied to both controls despite them being different");
// Compiler Error, no such member in interface
ctrl.Clicks = 0;
// Runtime Error because 1 class is in fact not a button which makes this cast invalid
((MyButton)ctrl).Clicks = 0;
/* the solution is to check the type first.
This is usually considered bad practice since
it's a symptom of poor abstraction */
var button = ctrl as MyButton;
if(button != null)
button.Clicks = 0; // no errors
}
明示的実装による「非表示」メンバー
あなたが気にしないメンバーが多すぎると、インターフェイスがあなたのクラスを汚染するとき、あなたはそれを嫌いませんか?さて、私は解決策を得ました!明示的な実装
public interface IMessageService {
void OnMessageRecieve();
void SendMessage();
string Result { get; set; }
int Encoding { get; set; }
// yadda yadda
}
通常、このようなクラスを実装します。
public class MyObjectWithMessages : IMessageService {
public void OnMessageRecieve(){
}
public void SendMessage(){
}
public string Result { get; set; }
public int Encoding { get; set; }
}
すべてのメンバーは公開されています。
var obj = new MyObjectWithMessages();
// why would i want to call this function?
obj.OnMessageRecieve();
答え:私はしません。したがって、どちらも宣言されるべきではありませんが、単純にメンバーを非公開として宣言すれば、コンパイラはエラーをスローします
ソリューションは明示的な実装を使用することです:
public class MyObjectWithMessages : IMessageService{
void IMessageService.OnMessageRecieve() {
}
void IMessageService.SendMessage() {
}
string IMessageService.Result { get; set; }
int IMessageService.Encoding { get; set; }
}
だから、あなたは必要に応じてメンバーを実装しました。メンバーを公開することはありません。
var obj = new MyObjectWithMessages();
/* error member does not exist on type MyObjectWithMessages.
* We've succesfully made it "private" */
obj.OnMessageRecieve();
明示的に実装していても、メンバーにアクセスしたい場合は、オブジェクトをインターフェースにキャストするだけで済みます。
((IMessageService)obj).OnMessageRecieve();
IComparable インタフェースの実装例として
インターフェイスは、あなたが実際にそれらのように見えるまで抽象的に見えることがあります。 IComparable
とIComparable<T>
は、インターフェイスがなぜ私たちに役立つのかの素晴らしい例です。
オンラインストアのプログラムでは、購入できるさまざまなアイテムがあるとしましょう。各項目には名前、ID番号、価格があります。
public class Item {
public string name; // though public variables are generally bad practice,
public int idNumber; // to keep this example simple we will use them instead
public decimal price; // of a property.
// body omitted for brevity
}
私たちはItem
List<Item>
中に格納しています。私たちのプログラムでは、リストをID番号でソートしたいのです。独自のソートアルゴリズムをSort()
代わりに、 List<T>
既にあるSort()
メソッドを代わりに使用できます。しかし、私たちのItem
クラスは今のところ、リストをソートする順序をList<T>
が理解する方法はありません。ここにIComparable
インターフェイスが入ってきます。
正しく実装するためCompareTo
方法、 CompareTo
パラメータが「より大きい」である場合、パラメータは現在のゼロ「未満」である場合、それらが等しい場合正数を返し、負の数べきです。
Item apple = new Item();
apple.idNumber = 15;
Item banana = new Item();
banana.idNumber = 4;
Item cow = new Item();
cow.idNumber = 15;
Item diamond = new Item();
diamond.idNumber = 18;
Console.WriteLine(apple.CompareTo(banana)); // 11
Console.WriteLine(apple.CompareTo(cow)); // 0
Console.WriteLine(apple.CompareTo(diamond)); // -3
Item
のインターフェイス実装の例を次に示します。
public class Item : IComparable<Item> {
private string name;
private int idNumber;
private decimal price;
public int CompareTo(Item otherItem) {
return (this.idNumber - otherItem.idNumber);
}
// rest of code omitted for brevity
}
サーフェスレベルでは、項目のCompareTo
メソッドは単にID番号の差を返しますが、実際には上記のことは何ですか?
List<Item>
オブジェクトに対してSort()
を呼び出すと、オブジェクトを配置する順序を決定する必要があるときにList
は自動的にItem
のCompareTo
メソッドを呼び出します。さらに、 List<T>
ほかに、 2つの異なるItem
互いに比較する能力を定義しているので、2つのオブジェクトを比較する能力を必要とするものは、 Item
と連携します。