수색…


통사론

  • 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에서 도입되었습니다. 확장 메서드는 새로운 파생 형식을 만들거나 다시 컴파일하거나 원래 형식을 수정하지 않고 기존 형식에 동작을 확장하고 추가합니다. 특히 개선하려는 유형의 소스를 수정할 수없는 경우에 유용합니다. 시스템 유형, 제 3자가 정의한 유형 및 사용자가 직접 정의한 유형에 대해 확장 메소드를 작성할 수 있습니다. 확장 메서드는 마치 원래 형식의 멤버 메서드 인 것처럼 호출 할 수 있습니다. 이것은 유창한 인터페이스 를 구현하기 위해 사용 된 Method Chaining을 허용합니다.

확장 메소드는 확장되는 원래 유형과 다른 정적 클래스에 정적 메소드 를 추가하여 작성됩니다. 확장 메소드를 보관 유지하는 정적 클래스는, 확장 메소드를 보관 유지하는 목적으로 작성되는 일이 있습니다.

확장 메서드는 확장되는 원본 형식을 지정하는 특수한 첫 번째 매개 변수를 사용합니다. 첫 번째 변수는 키워드 장식 this (특별한 별개의 사용 구성 this - 그것은이 사용에서 서로 다른 이해되어야 C에서 # this 현재 오브젝트 인스턴스의 멤버에게 허용 참조).

다음 예제에서 확장되는 원래 유형은 클래스 string 입니다. Shorten() 메서드는 String 을 확장하여 Shorten() 기능을 추가로 제공합니다. static 메소드 StringExtensions 가 확장 메소드를 보관 유지하기 위해서 작성되었습니다. 확장 메소드 Shorten() 은 특별히 표시된 첫 번째 매개 변수를 통한 string 의 확장임을 보여줍니다. Shorten() 메서드가 string 확장한다는 것을 나타내려면 첫 번째 매개 변수에 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 Extensions :

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에서의 라이브 데모


동일한 서명을 가진 두 개의 확장 함수가 있고 그 중 하나가 동일한 네임 스페이스에 있으면 그 중 하나가 우선 순위가 지정됩니다. 다른 한편으로는, 양쪽 모두를 using 액세스하는 경우, 다음의 메세지와 함께 컴파일 시간 오류가 발생합니다.

다음 메서드 또는 속성 사이의 호출이 모호합니다.


originalTypeInstance.ExtensionMethod() 를 통해 확장 메서드를 호출하는 구문상의 편의는 선택적 편의입니다. 이 메서드는 일반적인 방식으로 호출 될 수도 있으므로 특수한 첫 번째 매개 변수가 메서드의 매개 변수로 사용됩니다.

즉, 다음 작업 모두 :

//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);

확장 메서드를 정적 메서드로 호출해야하는 경우

확장 메소드를 정적 메소드로 사용해야하는 시나리오는 여전히 있습니다.

  • 멤버 메서드와의 충돌을 해결합니다. 새로운 버전의 라이브러리가 동일한 서명을 가진 새로운 멤버 메소드를 도입 할 경우 이런 일이 발생할 수 있습니다. 이 경우 컴파일러에서 멤버 메서드를 사용하는 것이 좋습니다.
  • 같은 서명으로 다른 확장 메소드와의 충돌을 해결합니다. 이것은 두 라이브러리가 유사한 확장 메소드를 포함하고 확장 메소드가있는 두 클래스의 이름 공간이 동일한 파일에서 사용되는 경우에 발생할 수 있습니다.
  • 확장 메서드를 메서드 그룹으로 대리자 매개 변수로 전달합니다.
  • 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 참조를 확장 메소드가 호출 될 때 null 참조, 그것은 throw하지 않습니다 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 (또는 internal) 멤버 만 볼 수 있습니다.

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 , internal 구현) 필드, 속성 및 메서드에만 액세스 할 수 있습니다.

일반 확장 메소드

확장 메소드는 다른 메소드와 마찬가지로 generics를 사용할 수 있습니다. 예 :

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 인수와 동일한 형식을 갖는 값을 반환하면 호환되는 서명으로 하나 이상의 메서드 호출을 "연결"하는 데 사용할 수 있습니다. 이는 밀폐형 및 / 또는 원시 형에 유용 할 수 있으며 메서드 이름이 자연 언어와 비슷한 경우 "유창한"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 개의 목록 비교

다음 확장 메서드를 사용하여 동일한 형식의 두 IList 인스턴스의 내용을 비교할 수 있습니다.

기본적으로 항목은 목록 및 항목 자체의 순서에 따라 비교되며 isOrdered 매개 변수에 false를 전달하면 순서에 관계없이 항목 자체 만 비교됩니다.

이 메서드가 작동하려면 제네릭 형식 ( 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;
    }
}

열거 형을 사용한 확장 메서드

확장 메서드는 열거 형에 기능을 추가하는 데 유용합니다.

한 가지 일반적인 용도는 변환 방법을 구현하는 것입니다.

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.

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 코드 및 혼합 기능이 가능합니다.

확장 메서드를 사용하면 인터페이스 자체에 핵심 필수 기능 만 포함시키고 확장 메서드로 편리한 메서드와 오버로드를 정의 할 수 있으므로 인터페이스 정의를 단순화 할 수 있습니다. 메소드가 적은 인터페이스는 새 클래스에서 구현하기가 쉽습니다. 오버로드를 인터페이스에 포함시키지 않고 확장으로 직접 보관하면 코드가 희박해질 수 있으므로 보편적 인 코드를 모든 구현에 직접 복사 할 필요가 없습니다. 이것은 사실 C #이 지원하지 않는 mixin 패턴과 유사합니다.

IEnumerable<T> 에 대한 System.Linq.Enumerable 의 확장은 이에 대한 좋은 예입니다. IEnumerable<T> 는 구현 클래스가 Generic 및 Non-generic GetEnumerator() 두 가지 메서드 만 구현하도록 요구합니다. 그러나 System.Linq.EnumerableIEnumerable<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.

정적 메서드 및 콜백과 함께 Extension 메서드 사용

확장 메소드를 다른 코드를 감싸는 함수로 사용하는 것을 고려해보십시오. 다음은 정적 메소드와 확장 메소드를 사용하여 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
        }
    }
}

인터페이스의 확장 메소드

확장 메서드의 유용한 기능 중 하나는 인터페이스에 대한 일반적인 메서드를 만들 수 있다는 것입니다. 일반적으로 인터페이스는 공유 구현을 가질 수 없지만 가능한 확장 메소드가 있습니다.

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 의, 그래서있을 필요가 없도록는이 방법을 수행 할 수 있습니다 FeetDriven 에서 IVehicle 모든 아이들을 위해 같은 방법으로 구현 될 것입니다 정의.

확장 메서드를 사용하여 아름다운 매퍼 클래스 만들기

확장 메서드를 사용하여 더 좋은 매퍼 클래스를 만들 수 있습니다.

 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