Section 48.4: Generic 확장 메소드

다른 메소드들과 마찬가지로, 확장 메소드 역시 generic 을 사용할 수 있다. 다음 예제를 확인한다:

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

Demo 확인하기

마찬가지로, 복수개의 Type Argument 를 갖는 경우는 다음과 같이 작성할 수 있다:

public static TU GenericExt<T, TU>(this T obj) { TU ret = default(TU); // obj 에 대한 작업을 수행한다 return ret; }

이는 다음과 같이 호출할 수 있다:

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

Demo 확인하기

다중 generic 타입 중에서 일부만이 bind 된 타입에 대해서도 마찬가지로 확장 메소드를 만들 수 있다:

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

Demo 확인하기

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

Demo 확인하기

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

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

반응형

Section 48.3: 확장 메소드를 명시적으로 (explicitly) 사용하기

확장 메소드는 일반적인 정적 (static) 클래스 메소드처럼 사용될 수도 있다. 이러한 방식의 확장 메소드 호출은 간결함이 떨어지긴 하지만, 경우에 따라 필수적일 수 있다.

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

사용 예:

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

정적 메소드 방식으로 확장 메소드를 호출해야 하는 경우들

확장 메소드를 정적 메소드와 같이 호출하여야 할 필요가 있는 경우가 여전히 존재한다:

  • 멤버 메소드와의 충돌 (conflict) 처리. 이러한 충돌은 새로운 버전의 라이브러리에 확장 메소드와 동일한 이름의 메소드가 추가된 경우 발생할 수 있다. 이러한 경우, 컴파일러에 의해 멤버 메소드가 우선적으로 호출될 것이다.
  • 동일한 서명 (signature) 을 갖는 다른 확장 메소드와의 충돌 처리. 이는 두 라이브러리가 유사한 확장 메소드를 제공하고 해당 확장 메소드가 존재하는 네임스페이스가 동일한 파일에서 사용되는 경우 발생할 수 있다.
  • delegate 파라미터에 확장 메소드를 method group 형태로 전달하는 경우.
  • Reflection 을 이용하여 사용자 스스로의 biding 을 수행하는 경우.
  • Visual Studio 의 Immediate window 에서 확장 메소드를 사용하는 경우.

Using static

using static 지시어를 통해 특정 정적 클래스의 정적 멤버들을 전역 범위 (global scope) 로 가져오는 경우, 확장 메소드들은 그 대상에서 제외된다.

예제:

using static OurNamespace.StringExtensions; // 이전 예제의 클래스를 참조한다 // OK: 확장 메소드 문법이 여전히 동작한다. "Hello World".Shorten(5); // OK: 정적 메소드 문법이 여전히 동작한다. OurNamespace.StringExtensions.Shorten("Hello World", 5); // 컴파일 시점 에러: 확장 메소드를 클래스 지정 없이 정적으로 호출할 수 없다. Shorten("Hello World", 5);

만약 Shorten 메소드의 첫번째 파라미터에서 this 한정자 (modifier) 를 제거한다면, 위 예제의 마지막 라인 역시 컴파일이 될 것이다.

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

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

반응형

Section 48.2: Null 인지 확인하기

확장 (Extension) 메소드는 마치 인스턴스 메소드인것처럼 동작하는 정적 (static) 메소드이다. 그러나, null 인 참조 (reference) 에 대해 인스턴스 메소드를 호출했을때와는 다르게, 확장 메소드는 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(); // "" 을 반환한다 string anotherNullString = emptyString.NullIfEmpty(); // null 을 반환한다

Live Demo on .NET Fiddle

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

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

반응형

Section 48.1: 확장 (Extension) 메소드의 개요

확장 (Extension) 메소드는 C# 3.0 에서 처음 소개되었다. 확장 메소드를 이용하면 기존 타입을 상속하거나, 재컴파일하거나, 혹은 어떠한 방식으로든 수정하지 않고도 새로운 기능을 추가함으로써 확장시킬 수 있다. 특히 이는 기능을 개선하고자 하는 타입의 소스 코드를 수정할 수 없는 경우 특히 유용하게 사용할 수 있다. 확장 메소드는 시스템 타입, 서드파티에 의해 정의된 타입들과 사용자 스스로가 정의한 타입 모두에 대해 새로이 생성될 수 있다. 이러한 확장 메소드는 마치 원본 타입에서 원래 제공되는 멤버 메소드들과 마찬가지로 호출될 수 있다. 이를 활용하면 Method Chaining 을 통한 Fluent Interface 구현이 가능하다.

역주: Method Chaining 과 Fluent Interface 에 대한 간략한 설명은 이 링크 에서 찾아보실 수 있습니다.

확장 메소드는 확장하고자 하는 원본 타입과는 별개의 정적 (static) 클래스에 정적 메소드를 추가함으로써 생성할 수 있다. 이러한 확장 메소드가 포함된 정적 클래스는 오로지 정적 메소드들을 선언하기 위한 용도로만 생성되는 경우가 많다.

확장 메소드는 특별한 첫번째 파라미터를 가지는데, 이는 바로 확장하고자 하는 대상이 되는 원본 타입을 가리킨다. 이러한 첫번째 파라미터는 this 키워드를 이용해 특별히 나타내어진다 (C# 에서의 일반적인 this 사용과 구분된다 — 일반적인 this 는 현재 객체 인스턴스의 멤버를 가리키기 위해 사용된다).

다음 예제에서, 확장하고자 하는 대상 타입은 string 클래스이다. string 은 축약된 문자열을 반환하는 Shorten() 이라는 메소드가 추가되는 방식으로 확장되었다. 정적 클래스인 StringExtensions 은 해당 확장 메소드를 내부에 선언하기 위한 용도로 생성되었다. 확장 메소드인 Shorten()this 라고 특수하게 표시된 첫번째 파라미터를 통해 string 클래스를 확장하고 있음을 나타내고 있다. 따라서, 첫번째 파라미터의 전체 형식은 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() { // String.ToUpper() 메소드를 호출한다 var myString = "Hello World!".ToUpper(); // StringExtensions.Shorten() 확장 메소드를 호출한다 var newString = myString.Shorten(5); // 위 함수 호출은 사실 문법적 간편화 장치 (syntactic sugar) 에 불과하며 // 아래 예제와 기능적으로 동일하다는 점을 알아둘 필요가 있다 var newString2 = StringExtensions.Shorten(myString, 5); } }

.NET Fiddle 에서 라이브 데모 확인하기

확장 메소드의 첫번째 파라미터로 넘겨진 (this 키워드와 함께 기술된) 객체는 해당 확장 메소드가 호출될 대상 인스턴스를 나타낸다.

예를 들어, 아래의 코드 실행시:

"some string".Shorten(5);

넘겨진 파라미터들의 값들은 다음과 같을 것이다:

text: "some string" length: 5

유의할 점은, 확장 메소드는 사용하는 namespace 와 동일한 namespace 에 정의가 되거나, 혹은 확장 메소드를 사용하는 코드에 해당 namespace 가 명시적으로 불러들여 지거나, 아니면 확장 클래스가 namespace 를 갖지 않을때만 사용 가능하다는 점이다. .NET framework 가이드라인에서는 확장 클래스를 고유의 namespace 에 위치하도록 권장하고 있으나, 실제로 이로 인해 접근성 문제가 야기될 수도 있다.

위 특성으로 인해, 충돌 (conflict) 이 발생할 수 있는 확장 메소드를 명시적으로 불러들이지 않는 이상, 확장 메소드와 사용하는 라이브러리간의 충돌을 피할 수 있게 된다. LINQ 확장의 예를 살펴보자:

using System.Linq; // System.Linq namespace 의 확장 메소드를 사용할 수 있게된다 class Program { static void Main() { var ints = new int[] { 1, 2, 3, 4 }; // System.Linq namespace 로부터 Where() 확장 메소드를 호출한다 var even = ints.Where(x => x % 2 == 0); } }

.NET Fiddle 에서 라이브 데모 확인하기

C# 6.0 부터는, 확장 메소드를 포함하는 클래스에 대해 using static 지시어를 사용할 수 있다. 예제는 다음과 같다: using static System.Linq.Enumerable;. 이는 같은 namespace 의 다른 타입들을 현재 scope 에 불러들이지 않으면서도 해당 특정 클래스에 선언된 확장 메소드를 사용할 수 있게 해준다.

만약 동일한 서명 (signature) 을 갖는 클래스 메소드가 존재하는 경우에는, 컴파일러는 해당 메소드를 확장 메소드보다 우선하여 호출해준다. 아래 예제를 확인한다:

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(); // "From Test" 가 출력된다 } }

.NET Fiddle 에서 라이브 데모 확인하기

유의하여야 할 점은, 만약 동일한 서명(signature) 를 갖는 확장 메소드가 두개 존재하며, 그중 하나가 동일 네임스페이스에 속한 경우에는 해당 확장 메소드를 우선하여 사용하게 될 것이라는 점이다. 반면에, 두 메소드가 모두 using 키워드를 통하여 접근되는 경우에는, 아래와 같은 내용으로 컴파일 에러가 발생할 것이다:

The call is ambiguous between the following methods or properties

또한 알아둘 점은, originalTypeInstance.ExtensionMethod() 와 같이 문법적으로 간편화된 방식의 확장 메소드 호출은 선택적인 (optional) 편의기능이라는 점이다. 첫번째 특수 파라미터에 메소드가 호출될 객체를 전달하는 전통적인 방식의 호출 방법을 통해서도 해당 메소드를 호출할 수 있다.

따라서, 아래 두가지 예제 모두 정상 동작할 것이다:

// 확장 메소드가 string 클래스에 속한 것처럼 호출하기 -- string 클래스를 심리스 (seamless) 하게 확장할 수 있다 String s = "Hello World"; s.Shorten(5); // 전통적인 방법으로 두개의 파라미터를 static 메소드에 전달하는 방식의 호출 StringExtensions.Shorten(s, 5);
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

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

반응형

Chapter 48: 확장 (Extension) 메소드

파라미터 설명
this 확장 메소드의 첫번째 파라미터는 항상 this 키워드가 우선적으로 기술되어야 하며, 확장하고자 하는 객체의 "현재" 인스턴스를 나타내는 식별자를 그 뒤에 기술하여야 한다
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

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

반응형

Section 47.8: 메소드 접근 권한들

// static: 해당 클래스에 대해 생성된 인스턴스가 존재하지 않는 경우에도 호출 가능한 메소드 public static void MyMethod() // virtual: 상속받은 클래스에서 호출 및 재정의 (override) 가 가능한 메소드 public virtual void MyMethod() // internal: 동일 어셈블리 내에서만 접근 가능한 메소드 internal void MyMethod() // private: 동일 클래스 내에서만 접근 가능한 메소드 private void MyMethod() // public: 모든 클래스 및 어셈블리에서 접근 가능한 메소드 public void MyMethod() // protected: 동일 클래스나 해당 클래스를 상속받은 타입에서 접근 가능한 메소드 protected void MyMethod() // protected internal: 동일 어셈블리나 해당 클래스를 상속받은 타입에서 접근 가능한 메소드 protected internal void MyMethod()
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

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

반응형

Section 47.7: 메소드 오버로딩

정의 : 복수개의 메소드를 동일한 이름으로 파라미터 종류만 다르게 선언하는 것을 메소드 오버로딩이라 칭한다. 메소드 오버로딩은 다양한 파라미터 타입에 대해 동일한 목적의 함수 기능을 작성하고자 할 때 사용된다.

영향을 끼치는 인자들:

  • 파라미터의 갯수
  • 파리미터들의 타입
  • 반환 타입[^1]

다양한 파라미터를 인자로 받아 영역의 크기를 계산하여 반환해주는 Area 라는 이름의 메소드를 생각해보자.

예제:

public string Area(int value1) { return String.Format("Area of Square is {0}", value1 * value1); }

이 메소드는 하나의 파라미터륵 받아 문자열을 반환할 것이다. 이 메소드를 5라는 정수값을 파라미터로 넘기며 호출한다면, 반환 문자열은 "Area of Square is 25" 가 될 것이다.

public double Area(double value1, double value2) { return value1 * value2; }

유사하게, 두개의 double 값을 위 메소드에 넘긴다면 반환값은 두 값을 곱한 double 타입의 값이 될 것이다. 이는 직사각형의 면적을 구하는데 사용될수 있을 것이다.

public double Area(double value1) { return 3.14 * Math.Pow(value1,2); }

위 메소드는 원의 면적을 구하는데 사용될 수 있으며, 반지름 double 값을 이용해 면적을 마찬가지로 double 타입으로 반환하게 된다.

위 메소드들은 서로간에 충동을 야기하지 않고 각각 호출이 가능하다 - 컴파일러는 각 메소드 호출시의 파라미터를 보고 어느 버전의 Area 메소드가 호출되어야 할 지를 결정해 준다.

string squareArea = Area(2); double rectangleArea = Area(32.0, 17.5); double circleArea = Area(5.0); // 모두 유효한 호출이며 컴파일에 성공할 것이다.

[^1] 유의할 점은 반환값 하나만 가지고는 두개의 메소드를 구별할 수 없다는 점이다. 예를 들어 만약 동일한 파라미터 목록을 갖는 두개의 Area 메소드를 아래와 같이 선언한 경우를 확인하여 본다:

public string Area(double width, double height) { ... } public double Area(double width, double height) { ... } // 이 코드는 컴파일되지 않을 것이다.

만약 사용자의 클래스가 동일한 이름을 갖되 각기 다른 타입을 반환하게 만들 필요가 있다면, 인터페이스를 구현하고 명시적으로 사용 방식을 정의함으로써 불명확성 관련 (ambiguity) 문제를 해결할 수 있다.

public interface IAreaCalculatorString { public string Area(double width, double height); } public class AreaCalculator: IAreaCalculatorString { public string IAreaCalculatorString.Area(double width, double height) { ... } // 이제 IAreaCalculatorString 인터페이스를 통해 불리는 경우에만 // 위 메소드가 사용됨을 명시하였으므로 불명확성 문제를 해결할 수 있다. public double Area(double width, double height) { ... }
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

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

반응형

Section 47.6: 파라미터 기본값 (default parameter)

특정 파라미터들에 대해 생략 가능한 선택지를 제공하고자 한다면 파라미터 기본값 (default parameter) 을 사용할 수 있다:

static void SaySomething(string what = "ehh") { Console.WriteLine(what); } static void Main() { // "hello" 를 출력한다 SaySomething("hello"); // "ehh" 를 출력한다 SaySomething(); // 컴파일러는 이 부분이 SaySomething("ehh") 라고 작성된 것 처럼 컴파일해줄 것이다 }

파라미터 기본값이 적용된 메소드를 호출하면서 해당 파라미터를 생략한다면, 컴파일러는 기본값을 대신 입력해줄 것이다.

유의할 점은, 기본값을 갖는 파라미터는 순서상 기본값이 없는 파라미터 다음에 위치해야 한다는 점이다.

static void SaySomething(string say, string what = "ehh") { // 적합한 사용 Console.WriteLine(say + what); } static void SaySomethingElse(string what = "ehh", string say) { // 부적합한 사용 Console.WriteLine(say + what); }

주의: 위에 설명한 방식대로 동작이 이루어지기 때문에 (생략된 파라미터를 기본값으로 간주하여 컴파일), 파라미터 기본값은 경우에 따라 문제를 발생시키기도 한다. 만약 이미 존재하고 있던 파라미터 기본값을 다른 값으로 변경한 후 해당 메소드를 호출하는 모든 모듈을 다시 컴파일하지 않는다면, 이러한 기존 호출자들은 여전히 변경되기 전의, 해당 모듈이 컴파일됐던 시점의 파라미터 기본값을 사용하게 될 것이며 이는 일관되지 않은 동작을 초래할 수 있다.

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

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

반응형

+ Recent posts