サーチ…


構文

  • public static ReturnType MyExtensionMethod(このTargetTypeターゲット)
  • public static ReturnType MyExtensionMethod(このTargetTypeターゲット、TArg1 arg1、...)

パラメーター

パラメータ詳細
この拡張メソッドの最初のパラメータの前には必ずthisキーワードが続き、拡張するオブジェクトの「現在の」インスタンスを参照する識別子が続きます

備考

拡張メソッドは、オブジェクトインスタンス上でスタティックメソッドを型自体のメンバのように呼び出すことを可能にするシンタックスシュガーです。

拡張メソッドには、明示的なターゲットオブジェクトが必要です。拡張キーワードそのものからメソッドにアクセスするには、 thisキーワードを使用する必要があります。

拡張メソッドは静的宣言され、静的クラスに存在しなければなりません。

どの名前空間ですか?

拡張メソッドクラスのネームスペースの選択は、可視性と発見性のトレードオフです。

最も一般的に言及されているオプションは、拡張メソッドのカスタム名前空間を持つことです。しかし、これには、コードのユーザーが拡張メソッドが存在すること、およびそれらを見つける場所を知るように、コミュニケーションが必要です。

別の方法として、開発者がIntellisenseを使用して拡張メソッドを検出できるように名前空間を選択する方法があります。したがって、 Fooクラスを拡張したい場合は、拡張メソッドをFooと同じ名前空間に配置するのが理にかなっています。

"誰か他人"の名前空間を使用することを妨げるものは何もないことを認識することが重要です 。したがって、 IEnumerableを拡張する場合は、 System.Linq名前空間に拡張メソッドを追加できます。

これは必ずしも良い考えではありません。たとえば、ある特定のケースでは、一般的な型( bool IsApproxEqualTo(this double value, double other)を拡張することができますが、 System全体を '汚染する'ことはできません。この場合、ローカルの特定の名前空間を選択することが望ましいです。

最後に、拡張メソッドを名前空間に全く入れないことも可能です!

良い参照の質問: 拡張メソッドの名前空間をどうやって管理していますか?

適用範囲

拡張メソッドを作成して、可能なすべての入力に適していることを確認し、特定の状況に関連するだけでなく、拡張メソッドを作成するときは注意が必要です。たとえば、 stringなどのシステムクラスを拡張して、新しいコードを任意の文字列で使用できるようにすることができます。コードがドメイン固有の文字列形式でドメイン固有のロジックを実行する必要がある場合、その存在がシステム内の他の文字列を扱う呼び出し元を混乱させるため、拡張メソッドは適切ではありません。

次のリストに、拡張メソッドの基本的な機能とプロパティを示します

  1. これは静的メソッドでなければなりません。
  2. 静的クラスに配置する必要があります。
  3. これは、 "this"キーワードを.NETの型を持つ最初のパラメータとして使用し、このメソッドはクライアント側の指定された型インスタンスによって呼び出されます。
  4. それはまた、VS intellisenseによって示された。私たちは、ドットを押すと.タイプインスタンスの後、それはVS intellisenseで来る。
  5. 拡張メソッドは、使用されているのと同じ名前空間にあるか、usingステートメントでクラスの名前空間をインポートする必要があります。
  6. 拡張メソッドを持つクラスには任意の名前を付けることができますが、クラスは静的でなければなりません。
  7. 型に新しいメソッドを追加したいが、そのためのソースコードがない場合は、その型の拡張メソッドを使用して実装することです。
  8. 拡張している型と同じシグネチャメソッドを持つ拡張メソッドを作成した場合、拡張メソッドは決して呼び出されません。

拡張メソッド - 概要

拡張メソッドはC#3.0で導入されました。拡張メソッドは、新しい派生型を作成したり、再コンパイルしたり、元の型を変更せずに、既存の型に動作を拡張したり追加したりします。 これらは、強化するために探しているタイプのソースを変更できない場合に特に役立ちます。拡張メソッドは、システムタイプ、サードパーティによって定義されたタイプ、および自分で定義したタイプに対して作成することができます。拡張メソッドは、元の型のメンバーメソッドであるかのように呼び出すことができます。これにより、 Fluent Interfaceの実装に使用されるMethod Chainingが可能になります。

拡張メソッドは、拡張される元の型とは異なる静的クラスに 静的メソッドを追加することによって作成されます。拡張メソッドを保持する静的クラスは、拡張メソッドを保持する唯一の目的のために作成されることがよくあります。

拡張メソッドは、拡張される元の型を指定する特殊な第1パラメータをとります。この最初のパラメータはthisというキーワードで飾られています( thisは、C#で特別に使い分けられthisいますが、現在のオブジェクトインスタンスのメンバを参照できるようにするために使用してください)。

次の例では、拡張される元の型はクラスstringです。 Shorten()メソッドによってStringが拡張されました。ショートニングの追加機能を提供します。拡張クラスStringExtensionsは、拡張メソッドを保持するために作成されています。拡張メソッドShorten()は、特別にマークされた最初のパラメータを介してstringを拡張したものであることを示しています。 Shorten()メソッドがstring拡張していることを示すために、最初のパラメータにthisとマークされてthisます。したがって、最初のパラメータの完全な署名はthis string text 。ここで、 stringは拡張される元の型で、 textは選択されたパラメータ名です。

static class StringExtensions
{
    public static string Shorten(this string text, int length) 
    {
        return text.Substring(0, length);
    }
}

class Program
{
    static void Main()
    {
        // This calls method String.ToUpper()
        var myString = "Hello World!".ToUpper();

        // This calls the extension method StringExtensions.Shorten()
        var newString = myString.Shorten(5); 

        // It is worth noting that the above call is purely syntactic sugar
        // and the assignment below is functionally equivalent
        var newString2 = StringExtensions.Shorten(myString, 5);
    }
}

.NET Fiddleのライブデモ


拡張メソッドの最初の引数thisキーワードが付いている)として渡されたオブジェクトは、拡張メソッドが呼び出されたインスタンスです。

たとえば、このコードが実行されると、次のようになります。

"some string".Shorten(5);

引数の値は次のとおりです。

text: "some string"
length: 5

拡張メソッドは、それらの定義と同じ名前空間にある場合、名前空間が拡張メソッドを使用してコードによって明示的にインポートされた場合、または拡張クラスが名前空間なしである場合にのみ使用できます。 .NET Frameworkのガイドラインでは、拡張クラスを独自の名前空間に配置することを推奨しています。ただし、これは発見の問題につながる可能性があります。

これにより、競合する可能性のある名前空間が明示的に引き込まれない限り、拡張メソッドと使用されているライブラリとの間に矛盾は生じません。たとえば、 LINQ拡張

using System.Linq; // Allows use of extension methods from the System.Linq namespace

class Program
{
    static void Main()
    {
        var ints = new int[] {1, 2, 3, 4};

        // Call Where() extension method from the System.Linq namespace
        var even = ints.Where(x => x % 2 == 0); 
    }
}

.NET Fiddleのライブデモ


C#6.0以降、拡張メソッドを含むクラスに using staticディレクティブをusing staticすることもできます。たとえば、 using static System.Linq.Enumerable; 。これにより、同じ名前空間の他の型をスコープに持たせることなく、その特定のクラスの拡張メソッドを利用できるようになります。


同じシグネチャを持つクラスメソッドが利用可能な場合、コンパイラは拡張メソッド呼び出しより優先します。例えば:

class Test
{
   public void Hello()
   {
       Console.WriteLine("From Test");
   }
}

static class TestExtensions
{
    public static void Hello(this Test test)
    {
        Console.WriteLine("From extension method");
    }
}

class Program
{
    static void Main()
    {
        Test t = new Test();
        t.Hello(); // Prints "From Test"
    }
}

.NET Fiddleのライブデモ


同じシグネチャを持つ2つの拡張関数があり、そのうちの1つが同じ名前空間にある場合、その関数が優先されることに注意してください。両者がによってアクセスされる一方、 using 、コンパイル時エラーがメッセージと共に結果として起きるであろう。

この呼び出しは、以下のメソッドまたはプロパティ間で曖昧です


originalTypeInstance.ExtensionMethod()を介して拡張メソッドを呼び出す場合の構文上の利便性はオプションです。メソッドは、従来の方法で呼び出すこともできます。そのため、特殊な第1パラメータがメソッドのパラメータとして使用されます。

つまり、次の両方の作業:

//Calling as though method belongs to string--it seamlessly extends string
String s = "Hello World";
s.Shorten(5);  

//Calling as a traditional static method with two parameters
StringExtensions.Shorten(s, 5);

明示的に拡張メソッドを使用する

拡張メソッドは、通常の静的クラスメソッドのように使用することもできます。拡張メソッドを呼び出すこの方法は、より冗長ですが、場合によっては必要です。

static class StringExtensions
{
    public static string Shorten(this string text, int length) 
    {
        return text.Substring(0, length);
    }
}

使用法:

var newString = StringExtensions.Shorten("Hello World", 5);

拡張メソッドを静的メソッドとして呼び出すタイミング

拡張メソッドを静的メソッドとして使用する必要があるシナリオはまだあります。

  • メンバーメソッドとの競合の解決。これは、ライブラリの新しいバージョンで同じシグネチャを持つ新しいメンバメソッドが導入された場合に発生します。この場合、メンバメソッドはコンパイラによって優先されます。
  • 同じ署名を持つ別の拡張メソッドとの競合の解決。これは、2つのライブラリに同様の拡張メソッドが含まれていて、拡張メソッドを持つ両方のクラスの名前空間が同じファイルで使用されている場合に発生します。
  • メソッドグループとしての拡張メソッドを委譲パラメータに渡します。
  • Reflectionを通してあなた自身の束縛をしています。
  • Visual Studioのイミディエイトウィンドウで拡張メソッドを使用する。

静的な使用

using staticディレクティブを使用して静的クラスの静的メンバーをグローバルスコープにすると、拡張メソッドはスキップされます。例:

using static OurNamespace.StringExtensions; // refers to class in previous example

// OK: extension method syntax still works.
"Hello World".Shorten(5);
// OK: static method syntax still works.
OurNamespace.StringExtensions.Shorten("Hello World", 5);
// Compile time error: extension methods can't be called as static without specifying class.
Shorten("Hello World", 5);

Shortenメソッドの最初の引数からthis修飾子を削除すると、最後の行がコンパイルされます。

ヌルチェック

拡張メソッドは、インスタンスメソッドのように動作する静的メソッドです。しかし、上のインスタンスメソッドを呼び出すときに何が起こるかとは違ってnull拡張メソッドを使用して呼び出され、参照、 null参照、それはスローされませんNullReferenceException 。これは、いくつかのシナリオでは非常に便利です。

たとえば、次の静的クラスを考えてみましょう。

public static class StringExtensions
{
    public static string EmptyIfNull(this string text)
    {
        return text ?? String.Empty;
    }

    public static string NullIfEmpty(this string text)
    {
        return String.Empty == text ? null : text;
    }
}
string nullString = null;
string emptyString = nullString.EmptyIfNull();// will return ""
string anotherNullString = emptyString.NullIfEmpty(); // will return null

.NET Fiddleのライブデモ

拡張メソッドは、拡張クラスのパブリック(または内部)メンバーのみを参照できます

public class SomeClass
{
    public void DoStuff()
    {
        
    }

    protected void DoMagic()
    {
        
    }
}

public static class SomeClassExtensions
{
    public static void DoStuffWrapper(this SomeClass someInstance)
    {
        someInstance.DoStuff(); // ok
    }

    public static void DoMagicWrapper(this SomeClass someInstance)
    {
        someInstance.DoMagic(); // compilation error
    }
}

拡張メソッドは単なる構文的な砂糖であり、実際に拡張するクラスのメンバーではありません。これは、カプセル化を破ることができないことを意味します。 public (または同じアセンブリ、 internal実装されている場合)フィールド、プロパティ、およびメソッドにのみアクセスできます。

汎用拡張メソッド

他のメソッドと同じように、拡張メソッドはジェネリックを使用できます。例えば:

static class Extensions
{
    public static bool HasMoreThanThreeElements<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Take(4).Count() > 3;
    }
}

それを呼び出すのは次のようなものです:

IEnumerable<int> numbers = new List<int> {1,2,3,4,5,6};
var hasMoreThanThreeElements = numbers.HasMoreThanThreeElements();

デモを見る

同様に複数の型引数の場合:

public static TU GenericExt<T, TU>(this T obj)
{
     TU ret = default(TU);
     // do some stuff with obj
     return ret;
}

それを呼び出すことは次のようになります:

IEnumerable<int> numbers = new List<int> {1,2,3,4,5,6};
var result = numbers.GenericExt<IEnumerable<int>,String>();

デモを見る

また、部分的にバインドされた型の拡張メソッドを、複数の汎用型で作成することもできます。

class MyType<T1, T2>
{
}

static class Extensions
{
    public static void Example<T>(this MyType<int, T> test)
    {        
    }
}

それを呼び出すことは次のようになります:

MyType<int, string> t = new MyType<int, string>();
t.Example();

デモを見る

あなたはまた、との型制約を指定することができます: where

public static bool IsDefault<T>(this T obj) where T : struct, IEquatable<T>
{
     return EqualityComparer<T>.Default.Equals(obj, default(T));
}

呼び出しコード:

int number = 5;
var IsDefault = number.IsDefault();

デモを見る

静的型に基づいた拡張メソッドのディスパッチ

動的(実行時型)ではなく、静的(コンパイル時)型が使用され、パラメータが一致します。

public class Base 
{ 
    public virtual string GetName()
    {
        return "Base";
    }
}

public class Derived : Base
{ 
    public override string GetName()
    {
        return "Derived";
    }
}

public static class Extensions
{
    public static string GetNameByExtension(this Base item)
    {
        return "Base";
    }

    public static string GetNameByExtension(this Derived item)
    {
        return "Derived";
    }
}

public static class Program   
{
    public static void Main()
    {
        Derived derived = new Derived();
        Base @base = derived;

        // Use the instance method "GetName"
        Console.WriteLine(derived.GetName()); // Prints "Derived"
        Console.WriteLine(@base.GetName()); // Prints "Derived"

        // Use the static extension method "GetNameByExtension"
        Console.WriteLine(derived.GetNameByExtension()); // Prints "Derived"
        Console.WriteLine(@base.GetNameByExtension()); // Prints "Base"
    }
}

.NET Fiddleのライブデモ

また、静的型に基づくディスパッチでは、 dynamicオブジェクトに対して拡張メソッドを呼び出すことはできません。

public class Person
{
    public string Name { get; set; }
}

public static class ExtenionPerson
{
    public static string GetPersonName(this Person person)
    {
        return person.Name;
    }
}

dynamic person = new Person { Name = "Jon" };
var name = person.GetPersonName(); // RuntimeBinderException is thrown

拡張メソッドは動的コードではサポートされていません。

static class Program
{
    static void Main()
    {
        dynamic dynamicObject = new ExpandoObject();

        string awesomeString = "Awesome";

        // Prints True
        Console.WriteLine(awesomeString.IsThisAwesome());

        dynamicObject.StringValue = awesomeString;

        // Prints True
        Console.WriteLine(StringExtensions.IsThisAwesome(dynamicObject.StringValue)); 
        
        // No compile time error or warning, but on runtime throws RuntimeBinderException
        Console.WriteLine(dynamicObject.StringValue.IsThisAwesome());
    }
}

static class StringExtensions
{
    public static bool IsThisAwesome(this string value)
    {
        return value.Equals("Awesome");
    }
}

理由は[ダイナミックコードからの拡張メソッドの呼び出し]は機能しません。なぜなら、動的ではないコード拡張メソッドは、コンパイラが知っているすべてのクラスを検索して、一致する拡張メソッドを持つ静的クラス。検索はネストスペースネストに基づいて順番に進み、各ネームスペースでディレクティブを使用using利用可能になります。

それは、正しく解決動的拡張メソッドの呼び出しを得るために、何とかDLRは、すべての名前空間のネストとどのような実行時に知っていなければならないことを意味usingディレクティブは、ソースコードにありました。私たちは、その情報をすべてコールサイトにエンコードするためのメカニズムを用意していません。私たちはそのような仕組みを考案したと思っていましたが、コストが高過ぎて、それに値するスケジュールリスクがあまりにも大きかったと判断しました。

ソース

強力な型付きラッパーとしての拡張メソッド

拡張メソッドは、辞書のようなオブジェクトのための強く型付けされたラッパーを書くために使用することができます。たとえば、キャッシュ、 HttpContext.Items at cetera ...

public static class CacheExtensions
{
    public static void SetUserInfo(this Cache cache, UserInfo data) => 
        cache["UserInfo"] = data;

    public static UserInfo GetUserInfo(this Cache cache) => 
        cache["UserInfo"] as UserInfo;
}

このアプローチでは、コードベース全体のキーとして文字列リテラルを使用する必要がなくなり、読み取り操作中に必要な型にキャストする必要がなくなります。全体的には、より安全で強く型付けされた、辞書などの疎結合オブジェクトとやり取りする方法を作成します。

連鎖の拡張メソッド

拡張メソッドがthis引数と同じ型を持つ値を返すとき、それは互換性のある署名で1つ以上のメソッド呼び出しを「連鎖する」ために使用できます。これは、密封型やプリミティブ型の場合に便利であり、メソッド名が自然言語のように読めば、いわゆる「流暢」なAPIを作成することができます。

void Main()
{
    int result = 5.Increment().Decrement().Increment(); 
    // result is now 6
}

public static class IntExtensions 
{
    public static int Increment(this int number) {
        return ++number;
    }

    public static int Decrement(this int number) {
        return --number;
    }
}

またはこのように

void Main()
{
    int[] ints = new[] { 1, 2, 3, 4, 5, 6};
    int[] a = ints.WhereEven();
    //a is { 2, 4, 6 };
    int[] b = ints.WhereEven().WhereGreaterThan(2);
    //b is { 4, 6 };
}

public static class IntArrayExtensions
{
    public static int[] WhereEven(this int[] array)
    {
        //Enumerable.* extension methods use a fluent approach
        return array.Where(i => (i%2) == 0).ToArray();
    }

    public static int[] WhereGreaterThan(this int[] array, int value)
    {
        return array.Where(i => i > value).ToArray();
    }
}

インタフェースと組み合わせた拡張メソッド

インプリメンテーションはクラスの外部に格納することができ、クラスにいくつかの機能を追加するために必要なのは、インタフェースを持つクラスをデコレートすることだけです。

public interface IInterface
{
   string Do()
}

public static class ExtensionMethods{
    public static string DoWith(this IInterface obj){
      //does something with IInterface instance
    }
}

public class Classy : IInterface
{
   // this is a wrapper method; you could also call DoWith() on a Classy instance directly,
   // provided you import the namespace containing the extension method
   public Do(){
       return this.DoWith();
   }
}

次のように使用します。

 var classy = new Classy();
 classy.Do(); // will call the extension
 classy.DoWith(); // Classy implements IInterface so it can also be called this way

IList 拡張メソッドの例:2リストの比較

次の拡張メソッドを使用して、同じ型の2つのIListインスタンスの内容を比較できます。

デフォルトでは、項目はリスト内の順序と項目自体に基づいて比較され、falseをisOrderedパラメータに渡すと、順序に関係なく項目自体が比較されます。

この方法が機能するために、ジェネリックタイプ( T )両方オーバーライドする必要がありEqualsGetHashCode方法を。

使用法:

List<string> list1 = new List<string> {"a1", "a2", null, "a3"};
List<string> list2 = new List<string> {"a1", "a2", "a3", null};

list1.Compare(list2);//this gives false
list1.Compare(list2, false);//this gives true. they are equal when the order is disregarded

方法:

public static bool Compare<T>(this IList<T> list1, IList<T> list2, bool isOrdered = true) 
{
    if (list1 == null && list2 == null)
        return true;
    if (list1 == null || list2 == null || list1.Count != list2.Count)
        return false;

    if (isOrdered)
    {
        for (int i = 0; i < list2.Count; i++)
        {
            var l1 = list1[i]; 
            var l2 = list2[i];
            if (
                 (l1 == null && l2 != null) || 
                 (l1 != null && l2 == null) || 
                 (!l1.Equals(l2)))
            {
                    return false;
            }
        }
        return true;
    }
    else
    {
        List<T> list2Copy = new List<T>(list2);
        //Can be done with Dictionary without O(n^2)
        for (int i = 0; i < list1.Count; i++)
        {
            if (!list2Copy.Remove(list1[i]))
                return false;
        }
        return true;
    }
}

Enumerationを使用した拡張メソッド

拡張メソッドは、機能を列挙に追加するのに便利です。

1つの一般的な用途は、変換方法を実装することです。

public enum YesNo
{
    Yes,
    No,
}

public static class EnumExtentions
{
    public static bool ToBool(this YesNo yn)
    {
        return yn == YesNo.Yes;
    }
    public static YesNo ToYesNo(this bool yn)
    {
        return yn ? YesNo.Yes : YesNo.No;
    }
}

これで、enum値を別のタイプに素早く変換できます。この場合はブールです。

bool yesNoBool = YesNo.Yes.ToBool(); // yesNoBool == true
YesNo yesNoEnum = false.ToYesNo();   // yesNoEnum == YesNo.No

あるいは、拡張メソッドを使用してメソッドのようなプロパティを追加することができます。

public enum Element
{
    Hydrogen,
    Helium,
    Lithium,
    Beryllium,
    Boron,
    Carbon,
    Nitrogen,
    Oxygen
    //Etc
}

public static class ElementExtensions
{
    public static double AtomicMass(this Element element)
    {
        switch(element)
        {
            case Element.Hydrogen:  return 1.00794;
            case Element.Helium:    return 4.002602;
            case Element.Lithium:   return 6.941;
            case Element.Beryllium: return 9.012182;
            case Element.Boron:     return 10.811;
            case Element.Carbon:    return 12.0107;
            case Element.Nitrogen:  return 14.0067;
            case Element.Oxygen:    return 15.9994;
            //Etc
        }
        return double.Nan;
    }
}

var massWater = 2*Element.Hydrogen.AtomicMass() + Element.Oxygen.AtomicMass();

拡張機能とインターフェースにより、DRYコードとミックスインのような機能が有効になります

拡張メソッドを使用すると、インターフェイス自体にコア必須機能を含めるだけで、インターフェイス定義を簡素化でき、便利なメソッドとオーバーロードを拡張メソッドとして定義できるようになります。メソッドの数が少ないインタフェースは、新しいクラスで実装する方が簡単です。オーバーロードをインターフェイスに含めるのではなく、拡張機能としてそのまま保持することで、定型コードをすべての実装に直接コピーすることがなくなり、コードのDRYを維持できます。これは実際にはC#がサポートしていないmixinパターンに似ています。

System.Linq.EnumerableIEnumerable<T>への拡張は、これの素晴らしい例です。 IEnumerable<T>では、実装クラスでは汎用と非汎用の2つのメソッドGetEnumerator()を実装する必要があります。しかし、 System.Linq.Enumerableは、 IEnumerable<T>簡潔で明瞭な消費を可能にする拡張として、無数の有用なユーティリティを提供します。

以下は、拡張として提供される便利なオーバーロードを備えた非常に単純なインターフェースです。

public interface ITimeFormatter
{
   string Format(TimeSpan span);
}

public static class TimeFormatter
{
    // Provide an overload to *all* implementers of ITimeFormatter.
    public static string Format(
        this ITimeFormatter formatter,
        int millisecondsSpan)
        => formatter.Format(TimeSpan.FromMilliseconds(millisecondsSpan));
}

// Implementations only need to provide one method. Very easy to
// write additional implementations.
public class SecondsTimeFormatter : ITimeFormatter
{
   public string Format(TimeSpan span)
   {
       return $"{(int)span.TotalSeconds}s";
   }
}

class Program
{
    static void Main(string[] args)
    {
        var formatter = new SecondsTimeFormatter();
        // Callers get two method overloads!
        Console.WriteLine($"4500ms is rougly {formatter.Format(4500)}");
        var span = TimeSpan.FromSeconds(5);
        Console.WriteLine($"{span} is formatted as {formatter.Format(span)}");
    }
}

特別なケースを扱うための拡張メソッド

エクステンションメソッドは、そうでなければif / thenステートメントで呼び出し関数を乱雑にする必要のある、洗練されていないビジネスルールの処理を「隠す」ために使用できます。これは、拡張メソッドでnullを処理する場合と同様です。例えば、

public static class CakeExtensions
{
    public static Cake EnsureTrueCake(this Cake cake)
    {
        //If the cake is a lie, substitute a cake from grandma, whose cakes aren't as tasty but are known never to be lies. If the cake isn't a lie, don't do anything and return it.
        return CakeVerificationService.IsCakeLie(cake) ? GrandmasKitchen.Get1950sCake() : cake;
    }
}
Cake myCake = Bakery.GetNextCake().EnsureTrueCake();
myMouth.Eat(myCake);//Eat the cake, confident that it is not a lie.

静的メソッドとコールバックでの拡張メソッドの使用

他のコードをラップする関数として拡張メソッドを使用することを検討してください。ここでは、Try Catch構造をラップするための静的メソッドと拡張メソッドの両方を使用する優れた例です。あなたのコードの弾丸証明を作る...

using System;
using System.Diagnostics;

namespace Samples
{
    /// <summary>
    /// Wraps a try catch statement as a static helper which uses 
    /// Extension methods for the exception
    /// </summary>
    public static class Bullet
    {
        /// <summary>
        /// Wrapper for Try Catch Statement
        /// </summary>
        /// <param name="code">Call back for code</param>
        /// <param name="error">Already handled and logged exception</param>
        public static void Proof(Action code, Action<Exception> error)
        {
            try
            {
                code();
            }
            catch (Exception iox)
            {
                //extension method used here
                iox.Log("BP2200-ERR-Unexpected Error");
                //callback, exception already handled and logged
                error(iox);
            }
        }
        /// <summary>
        /// Example of a logging method helper, this is the extension method
        /// </summary>
        /// <param name="error">The Exception to log</param>
        /// <param name="messageID">A unique error ID header</param>
        public static void Log(this Exception error, string messageID)
        {
            Trace.WriteLine(messageID);
            Trace.WriteLine(error.Message);
            Trace.WriteLine(error.StackTrace);
            Trace.WriteLine("");
        }
    }
    /// <summary>
    /// Shows how to use both the wrapper and extension methods.
    /// </summary>
    public class UseBulletProofing
    {
        public UseBulletProofing()
        {
            var ok = false;
            var result = DoSomething();
            if (!result.Contains("ERR"))
            {
                ok = true;
                DoSomethingElse();
            }
        }

        /// <summary>
        /// How to use Bullet Proofing in your code.
        /// </summary>
        /// <returns>A string</returns>
        public string DoSomething()
        {
            string result = string.Empty;
            //Note that the Bullet.Proof method forces this construct.
            Bullet.Proof(() =>
            {
                //this is the code callback
                result = "DST5900-INF-No Exceptions in this code";
            }, error =>
            {
                //error is the already logged and handled exception
                //determine the base result
                result = "DTS6200-ERR-An exception happened look at console log";
                if (error.Message.Contains("SomeMarker"))
                {
                    //filter the result for Something within the exception message
                    result = "DST6500-ERR-Some marker was found in the exception";
                }
            });
            return result;
        }

        /// <summary>
        /// Next step in workflow
        /// </summary>
        public void DoSomethingElse()
        {
            //Only called if no exception was thrown before
        }
    }
}

インタフェースの拡張メソッド

拡張メソッドの便利な機能の1つは、インターフェイスの一般的なメソッドを作成できることです。通常、インタフェースは共有実装を持つことはできませんが、拡張メソッドを使用することができます。

public interface IVehicle
{
    int MilesDriven { get; set; }
}

public static class Extensions
{
    public static int FeetDriven(this IVehicle vehicle)
    {
        return vehicle.MilesDriven * 5028;
    }
}

この例では、 FeetDrivenメソッドは任意のIVehicleで使用できます。この方法のこのロジックはすべてのIVehicle適用されるため、この方法で行うことができ、 IVehicle定義にFeetDrivenがなくても、すべての子に対して同じ方法で実装されます。

拡張メソッドを使って美しいマッパークラスを作成する

拡張メソッドを使ってより良いマッパークラスを作成することができます。もし私がいくつかのDTOクラスを持っていたら

 public class UserDTO
 {
        public AddressDTO Address { get; set; }
 }

 public class AddressDTO
 {
        public string Name { get; set; }
 }

私は対応するビューモデルクラスにマップする必要があります

public class UserViewModel
{
    public AddressViewModel Address { get; set; }
}

public class AddressViewModel
{
    public string Name { get; set; }
}

私は下のように私のマッパークラスを作成することができます

public static class ViewModelMapper
{
      public static UserViewModel ToViewModel(this UserDTO user)
      {
            return user == null ?
                null :
                new UserViewModel()
                {
                    Address = user.Address.ToViewModel()
                    // Job = user.Job.ToViewModel(),
                    // Contact = user.Contact.ToViewModel() .. and so on
                };
      }

      public static AddressViewModel ToViewModel(this AddressDTO userAddr)
      {
            return userAddr == null ?
                null :
                new AddressViewModel()
                {
                    Name = userAddr.Name
                };
      }
}

その後、私はマッパを以下のように呼び出すことができます

    UserDTO userDTOObj = new UserDTO() {
            Address = new AddressDTO() {
                Name = "Address of the user"
            }
        };

    UserViewModel user = userDTOObj.ToViewModel(); // My DTO mapped to Viewmodel

ここの美しさは、すべてのマッピングメソッドが共通名(ToViewModel)を持ち、いくつかの方法で再利用できることです

拡張メソッドを使用して新しいコレクションタイプを構築する(例:DictList)

List<T>値を持つDictionaryようなネストされたコレクションのユーザビリティを向上させる拡張メソッドを作成することができます。

以下の拡張メソッドを考えてみましょう。

public static class DictListExtensions
{
    public static void Add<TKey, TValue, TCollection>(this Dictionary<TKey, TCollection> dict, TKey key, TValue value)
            where TCollection : ICollection<TValue>, new()
    {
        TCollection list;
        if (!dict.TryGetValue(key, out list))
        {
            list = new TCollection();
            dict.Add(key, list);
        }

        list.Add(value);
    }

    public static bool Remove<TKey, TValue, TCollection>(this Dictionary<TKey, TCollection> dict, TKey key, TValue value)
        where TCollection : ICollection<TValue>
    {
        TCollection list;
        if (!dict.TryGetValue(key, out list))
        {
            return false;
        }

        var ret = list.Remove(value);
        if (list.Count == 0)
        {
            dict.Remove(key);
        }
        return ret;
    }
}

次のように拡張メソッドを使用できます。

var dictList = new Dictionary<string, List<int>>();

dictList.Add("example", 5);
dictList.Add("example", 10);
dictList.Add("example", 15);

Console.WriteLine(String.Join(", ", dictList["example"])); // 5, 10, 15

dictList.Remove("example", 5);
dictList.Remove("example", 10);

Console.WriteLine(String.Join(", ", dictList["example"])); // 15

dictList.Remove("example", 15);

Console.WriteLine(dictList.ContainsKey("example")); // False

デモを見る



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow