Programming/C#

C# 4.0 변경점 - 선택적 매개변수/명명된 인자, dynamic 예약어

lee308812 2019. 4. 15. 18:53

[ C# 4.0 ]

- C# 4.0으로 만든 응용프로그램은 닷넷 프레임워크 4.0에서 실행된다. 닷넷 4.0이 기본 설치된 윈도우 운영체제는 없다.(확인 필요)

 

- 닷넷 4.0은 CLR 버전이 올라갔다는데 의미가 있다. 닷넷 4.0은 새롭게 CLR 4.0에서 운영되며 GAC(Global Assembly Cache)도 분리됐다. 이전까지는 닷넷 1.0 ~ 3.5용으로 빌드되는 모든 어셈블리가 GAC에 추가되는 경우 단일하게 "%windir$\assembly"로 등록됐지만 닷넷 4.0 이상을 대상으로 빌드된 어셈블리는 Microsoft.NET 폴더 아래의 assembly 폴더로 바뀌었다.

CLR 2.0 GAC 위치 : %windir%\assembly
CLR 4.0 GAC 위치 : %windir%\Microsoft.NET\assembly

- 따라서 공통 라이브러리 개발자는 같은 동작을 하는 어셈블리를 .NET 2.0, .NET 4.0 환경으로 각각 빌드해서 제공해야 한다. (GAC가 등록되는 경우가 아니라면 상관없다.) 또한, 닷넷 4.0의 개발환경은 Visual Studio 2010 부터 제공된다.

 

 

[ 선택적 매개변수와 명명된 인자 ]

- 메서드의 매개변수를 정의할 때, 기본값을 명시하여 정의할 수 있다.

using System;

namespace ConsoleApp1
{
    class Person
    {
        public void Output(string name, int age = 0, string address = "Korea")
        {
            Console.WriteLine(string.Format("{0}: {1} in {2}", name, age, address));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person();

            p.Output("Anders");
            p.Output("Winnie", 36);
            p.Output("Tom", 28, "Tibet");

            // 컴파일 오류 발생. age를 생략하고 address 인자를 전달할 수 없다.
            // p.Output("Tom", "Tibet");

            // 명명된 인자를 이용하면 원하는 값을 골라서 전달할 수 있다.
            // 순서가 지켜져야 할 필요도 없다.
            p.Output("Tom", address: "Tibet");
            p.Output(address: "Tibet", name: "Tom");
            p.Output(name: "Tom");
        }
    }
}

 

[ dynamic 예약어 ]

- 마이크로소프트는 기존의 C#, VB.NET, C++/CLI와 같은 정적 언어 말고도 동적 언어까지도 닷넷 프레임워크와 호환되도록 CLR을 바탕으로 한 DLR(Dynamic Language Runtime) 라이브러리를 내놓았다. 이로 인해 Ruby, Python 같은 동적 언어까지도 닷넷 프레임워크에서 실행될 수 있게 IronRuby, IronPython으로 포팅됐다.

 

- 동적 언어로 만들어진 프로그램의 타입 시스템을 C#과 같은 정적 언어에서 연동을 쉽게할 수 있도록 dynamic 예약어가 추가되었다. 또한 C#의 Dynamic 예약어를 통해 기존의 다른 문제까지도 자연스럽게 해결하게 된다.

 

- var 예약어는 C# 컴파일러가 빌드 시점에 초깃값과 대응되는 타입으로 치환하는 반면, dynamic 예약어는 해당 프로그램이 실행되는 시점에 타입을 결정한다.

var d = 5;
d = "test";   // 컴파일 오류
d.CallFunc(); // 컴파일 오류

dynamic d2 = 5;
d2 = "test"
d2.CallFunc(); // 컴파일시에는 오류가 발생하지 않음. 실행시에는 오류 발생

 

리플렉션 개선

 

- 리플렉션 기술은 아래와 같이 소스코드를 매우 복잡하게 만든다. 예를 들어 다음 코드에서

string txt = "Test Func";
bool result = txt.Contains("Test");

-  Contains 메서드를 리플렉션으로 호출하면 소스코드가 한층 더 복잡해진다.

using System;
using System.Reflection;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string txt = "Test Func";

            Type type = txt.GetType();

            MethodInfo containsMethodInfo = type.GetMethod("Contains");

            if(containsMethodInfo != null)
            {
                object returnValue = containsMethodInfo.Invoke(txt, new object[] { "Test" });
                bool callResult = (bool)returnValue;

                Console.WriteLine(callResult);
            }
        }
    }
}

- 리플렉션 기술은 dynamic의 근간을 이루고 있기 때문에 dynamic 예약어를 리플렉션의 간편 표기법 정도로 여기고 사용해도 무방하다. 

dynamic txt = "Test Func";
bool result = txt.Contains("Test");

Console.WriteLine(result);

- 이러한 특성은 확장 모듈(Plug-in)을 사용하기 쉽게 만들어준다. 기존에는 Assembly.LoadFrom 등으로 직접 로드한 어셈블리 안에 있는 타입의 메서드를 호출하려면 리플렉션을 이용해야 했으나, dynamic을 사용하면 확장 모듈로부터 생성된 객체를 dynamic 변수에 담아 사전에 정의된 메서드의 이름으로 호출하기만 하면 된다.

 

덕 타이핑

- 둘 이상의 타입에서 "동일한 이름"으로 제공되는 속성 또는 메서드가 있다고 할 때, 여기에 접근해야하는 코드를 작성하고 싶다면 보통은 다음과 같이 인터페이스나 부모 클래스를 공유하고 상속 관계를 이용해 호출할 수 있게 만든다.

using System;
using System.Reflection;

namespace ConsoleApp1
{
    interface IBird
    {
        void Fly();
    }

    class Duck : IBird
    {
        public void Fly()
        {
            Console.WriteLine("Duck fly");
        }
    }

    class Goose : IBird
    {
        public void Fly()
        {
            Console.WriteLine("Goose fly");
        }
    }

    class Program
    {
        static void StrongTypeCall(IBird bird)
        {
            bird.Fly();
        }

        static void Main(string[] args)
        {
            StrongTypeCall(new Duck());
            StrongTypeCall(new Goose());
        }
    }
}

하지만, 모든 타입이 이런 식의 구조적인 호출 관계를 따르지는 않는다. 예를 들어, string 타입과 List<T> 타입은 동일하게 IndexOf 메서드를 제공하지만, 두타입은 IndexOf 메서드를 위한 기반 타입을 상속 받은 것은 아니기 때문에 공통 타입이 없다. 이러한 경우 dynamic을 사용하면 아무런 문제 없이 처리된다.

using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    class Program
    {
        static int DuckTypingCall(dynamic target, dynamic item)
        {
            return target.IndexOf(item);
        }

        static void Main(string[] args)
        {
            string txt = "test func";
            List<int> list = new List<int> { 1, 2, 3, 4, 5 };

            Console.WriteLine(DuckTypingCall(txt, "test")); // 0
            Console.WriteLine(DuckTypingCall(list, 3));     // 2
        }
    }
}

 

 

동적 언어와의 타입 연동

- 현재 동적 언어로서 Visual studio에 가장 통합이 잘 된 언어는 파이썬의 CLR 버전인 IronPython 이므로 이를 대상으로 실습을 진행한다.

 

- Visual Studio에서 파이썬과 연동할 C# 프로젝트를 생성한 다음, Tool - Nuget 패키지 관리자 - 패키지 관리자 콘솔 메뉴를 선택하면 나오는 창에서 다음과 같은 명령을 실행한다.

PM> Install-Package IronPython
using System;
using IronPython.Hosting;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var scriptEngine = Python.CreateEngine();

            string code = @"print 'Hello Python'";

            scriptEngine.Execute(code);
        }
    }
}

 

- 본격적으로 소스코드에 dynamic을 사용하면 또 다른 수준의 연동이 가능하다. 가령 파이썬 소스코드에 정의된 함수를 C#에서 직접 호출할 수 있다. 물론 인자도 넘길 수 있고 반환값도 받을 수 있다.

using System;
using IronPython.Hosting;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var scriptEngine = Python.CreateEngine();
            var scriptScope = scriptEngine.CreateScope();

            // 파이썬에서 AddFunc 함수를 정의
            string code = @"
def AddFunc(a, b):
    print 'AddFunc called'
    return (a + b)
";
            scriptEngine.Execute(code, scriptScope);

            // 파이썬 엔진에서 해석된 AddFunc 참조를 dynamic 변수로 받고,
            dynamic addFunc = scriptScope.GetVariable("AddFunc");

            // dynamic 변수를 마치 델리게이트 타입인 것처럼 메서드를 호출
            // 결과적으로 C#에서 파이썬의 함수를 직접 실행
            int nResult = addFunc(5, 10);

            Console.WriteLine(nResult);

        }
    }
}

- 예제를 조금만 더 바꾸면 파이썬에서 C# 코드를 호출하게 만들 수도 있다.

using System;
using System.Collections.Generic;
using System.Linq;
using IronPython.Hosting;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var scriptEngine = Python.CreateEngine();
            var scriptScope = scriptEngine.CreateScope();

            List<string> list = new List<string>();
            scriptScope.SetVariable("myList", list);

            // 파이썬에서는 C#에서 전달받은 객체를 사용
            string code = @"
myList.Add('my')
myList.Add('python')";

            scriptEngine.Execute(code, scriptScope);

            // 파이썬에 의해 list에는 "my", "python"이라는 2개의 요소가 포함됨
            list.ForEach((item) => { Console.WriteLine(item); });
        }
    }
}

- Python에서 지원되는 module을 사용하려면 별도로 IronPython을 내려받아 사용해야 한다.