Section 48.12: 인터페이스와 확장 메소드를 활용한 DRY 원칙 기반의 코드 및 mix-in 과 유사한 기능 제공하기

확장 메소드를 이용하면, 인터페이스 자체에는 반드시 필요한 핵심 기능만을 포함시키고, 편의를 위한 메소드나 오버로드된 메소드는 확장 메소드를 별도로 선언함으로써 인터페이스를 간결하게 정의할 수 있다.

더 적은 수의 메소드를 갖는 인터페이스는 새로운 클래스에서 더욱 쉽게 구현 (implement) 할 수 있다. 오버로드된 메소드들을 인터페이스에 직접 포함하기보다는 확장 메소드로 별도 유지함을 통해, 모든 인터페이스 구현물 (implementation) 들에 표준 문안 (boilerplate) 코드를 복사해 넣을 필요를 없앨 수 있어 사용자의 코드가 DRY 원칙을 따를 수 있도록 한다. 이는 실제적으로 C# 에서 지원을 하지 않는 mix-in 패턴 과 유사하다.

System.Linq.Enumerable 내에 존재하는 IEnumerable<T> 에 대한 확장 메소드들이 이에 대한 훌륭한 예제이다. IEnumerable<T> 를 구현 (implement) 하는 클래스들은 오직 두가지 메소드를 구현하기만 하면 된다: generic 과 non-generic 버전의 GetEnumerator() 가 바로 그것이다. 하지만 System.Linq.Enumerable 에는 IEnumerable<T> 을 간결하고 명료하게 사용할 수 있도록 해주는 수많은 유용한 확장 메소드들이 제공된다.

아래 예제에서는 오버로드 (overload) 된 편의를 위한 확장메소드들이 제공되는 매우 간단한 인터페이스를 보여주고 있다.

public interface ITimeFormatter { string Format(TimeSpan span); } public static class TimeFormatter { // ITimeFormatter 를 구현하는 클래스들 모두에 대해 제공되는 오버로드된 메소드. public static string Format( this ITimeFormatter formatter, int millisecondsSpan) => formatter.Format(TimeSpan.FromMilliseconds(millisecondsSpan)); } // 해당 인터페이스를 구현자고자 할 때는 하나의 메소드만 제공하면 되므로 // 추가적인 구현물 (implementation) 을 작성하기가 매우 용이하다. 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(); // 호출자는 두가지의 오버로드된 메소드를 사용할 수 있다 Console.WriteLine($"4500ms is rougly {formatter.Format(4500)}"); var span = TimeSpan.FromSeconds(5); Console.WriteLine($"{span} is formatted as {formatter.Format(span)}"); } }
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 48.11: 동적 (dynamic) 코드에서의 확장 메소드 미지원

static class Program { static void Main() { dynamic dynamicObject = new ExpandoObject(); string awesomeString = "Awesome"; // True 를 출력한다 Console.WriteLine(awesomeString.IsThisAwesome()); dynamicObject.StringValue = awesomeString; // True 를 출력한다 Console.WriteLine(StringExtensions.IsThisAwesome(dynamicObject.StringValue)); // 컴파일 시점의 에러나 경고가 출력되진 않지만, 실행 시 RuntimeBinderException 이 발생된다 Console.WriteLine(dynamicObject.StringValue.IsThisAwesome()); } } static class StringExtensions { public static bool IsThisAwesome(this string value) { return value.Equals("Awesome"); } }

[동적 코드에서의 확장 메소드 호출] 이 동작하지 않는 이유는, 일반적인 비-동적 (non-dynamic) 코드에서는 확장 메소드 호출 시 컴파일러가 알 수 있는 모든 클래스들에 대해 일치하는 확장 메소드가 있는 정적 클래스를 찾는 전체 검색을 수행하기 때문이다. 이러한 검색은 namespace 중첩 탐색 및 각 namespace 내에 존재하는 using 지시어에 기반한 탐색을 순서대로 수행하는 방식으로 진행된다.

이것이 의미하는 바는, 동적 확장 메소드 호출을 정확하게 해석하기 위해서 DLR 이 실행 시점에 원래의 소스코드를 기반으로 한 모든 namespace 중첩 및 using 지시어 관련 정보를 알 수 있어야 한다는 의미이다. 현재 그러한 모든 정보를 손쉽게 각 호출 지점에 저장할 수 있는 체계가 마련되어 있지 않으며, 그러한 체계를 구현하기 위한 고려가 있긴 하였으나, 이는 과도한 비용과 일정상의 무리함에 비해 얻는 이득이 크지 않다고 판단되었다.

출처

본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 48.10: 인터페이스에 확장 메소드 조합하기

인터페이스와 함께 확장 메소드를 사용하는 것은 매우 편리한데, 이는 구현부를 클래스 외부에 위치시킬 수 있으며, 해당 기능을 추가하고자 하는 경우 클래스에 필요한 인터페이스를 상속시키기만 하면 되기 때문이다.

public interface IInterface { string Do() } public static class ExtensionMethods { public static string DoWith(this IInterface obj) { // IInterface 인스턴스에 대한 특정 기능을 수행한다 } } public class Classy: IInterface { // 아래는 단순한 래퍼 (wrapper) 메소드이다; `DoWith()` 메소드를 `Classy` 인스턴스에 대해 직접 호출할 수도 있는데, // 이를 위해서는 확장 메소드가 포함된 네임스페이스가 import 된 상태여야 한다 public Do() { return this.DoWith(); } }

아래와 같이 사용한다:

var classy = new Classy(); classy.Do(); // 확장 기능을 사용한다 classy.DoWith(); // `Classy` 가 `IInterface` 를 구현 (implement)하기에 이런 방식으로 호출 가능하다
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 48.9: 인터페이스에 확장 메소드 적용하기

확장 메소드의 유용한 기능 중 하나는, 바로 인터페이스에 대한 공통 구현부를 제공할 수 있다는 것이다. 일반적으로 인터페이스는 공통 구현부를 가질 수 없는데, 확장 메소드를 이용하면 이것이 가능해진다.

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 메소드를 인터페이스 내에 추가적으로 선언할 필요가 없다.

본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 48.8: 정적 (static) 타입에 기반한 확장 메소드 호출

확장 메소드의 첫번째 파라미터 타입 부합 여부는 동적 (런타임) 타입이 아닌 정적 (컴파일 타임) 타입에 따라 결정된다.

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; // "GetName" 인스턴스 메소드를 호출한다 Console.WriteLine(derived.GetName()); // "Derived" 가 출력된다 Console.WriteLine(@base.GetName()); // "Derived" 가 출력된다 // "GetNameByExtension" 정적 확장메소드를 호출한다 Console.WriteLine(derived.GetNameByExtension()); // "Derived" 가 출력된다 Console.WriteLine(@base.GetNameByExtension()); // "Base" 가 출력된다 } }

역주: 위 예제에서 @base 와 같이 변수 이름 앞에 @가 붙은 것은, 예약어 (reserved keyword) 와 동일한 이름의 변수를 사용하기 위함입니다. 다음 링크에서 좀 더 상세한 설명을 확인하실 수 있습니다: 링크

.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 예외가 발생한다
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 48.7: 열거형 (Enumeration) 과 확장 메소드

확장 메소드는 열거형 (enumeration) 에 추가적인 기능을 부여하고자 하는 경우 매우 유용하게 사용될 수 있다.

흔히 사용되는 예제는 바로 변환 (conversion) 메소드를 구현하는 것이다.

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; } }

이제 사용자는 열거형 값을 다른 타입으로 손쉽게 변환할 수 있다. 이 예제에서는 bool 로의 변환을 수행한다.

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

확장 메소드는 이 외에도 속성 (property) 과 유사하게 동작하는 메소드를 추가하기 위해 사용될 수 있다.

public enum Element { Hydrogen, Helium, Lithium, Beryllium, Boron, Carbon, Nitrogen, Oxygen // 기타 등등 } 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; // 기타 등등 } return double.Nan; } } var massWater = 2 * Element.Hydrogen.AtomicMass() + Element.Oxygen.AtomicMass();
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 48.6: 메소드 chaining 을 위한 확장 메소드 사용

확장 메소드가 자신에게 주어지는 this 파라미터와 동일한 타입의 값을 반환하는 경우, 이는 호환 가능한 서명 (signature) 를 갖는 하나 이상의 다른 메소드들을 체이닝 ("chain") 하는 방식으로 사용될 수 있다. 이는 sealed 혹은 기본 (primitive) 타입의 경우에 더욱 유용하며, 메소드 이름들이 인간들의 자연 언어처럼 읽혀질 경우 흔히 말하는 "fluent" API 를 만들 수 있게된다.

void Main() { int result = 5. Increment().Decrement().Increment(); // 결과값은 이제 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 는 { 2, 4, 6 } 값을 갖는다 int[] b = ints.WhereEven().WhereGreaterThan(2); // b { 4, 6 } 값을 갖는다 } public static class IntArrayExtensions { public static int[] WhereEven(this int[] array) { // Enumerable.* 확장 메소드는 fluent 접근 방식을 사용한다 return array.Where(i => (i % 2) == 0).ToArray(); } public static int[] WhereGreaterThan(this int[] array, int value) { return array.Where(i => i > value).ToArray(); } }
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

Section 48.5: 확장 메소드의 접근 가능 범위: public (혹은 internal) 멤버들

public class SomeClass { public void DoStuff() { } protected void DoMagic() { } } public static class SomeClassExtensions { public static void DoStuffWrapper(this SomeClass someInstance) { someInstance.DoStuff(); // 문제 없음 } public static void DoMagicWrapper(this SomeClass someInstance) { someInstance.DoMagic(); // 컴파일 에러 } }

확장 메소드는 단지 문법적 간편화 장치 (syntactic sugar) 일 뿐이며, 확장 대상이 되는 클래스의 실제 멤버로 취급되지는 않는다. 이것이 의미하는 바는, 확장 메소드가 encapsulation 을 무시할 수는 없다는 것이다 — 이들은 단지 public (혹은 동일한 어셈블리에 구현된 경우는 internal 까지 포함) 필드나 속성값, 메소드들에 대해서만 접근 권한을 갖게 된다.

본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

+ Recent posts