[C#] 26. var 키워드와 dynamic 키워드


Study/C#  2021. 9. 10. 16:18

안녕하세요. 명월입니다.


이 글은 C#의 var 키워드와 dynamic 키워드에 대한 글입니다.


이전 글에서 제가 프로그램의 Stack 메모리와 Heap 메모리의 관계에 대해서 설명한 적이 있습니다.

링크 - [C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null


변수에서 선언되는 데이터 타입은 Heap에 생성되는 인스턴스의 포인터 주소라고 설명했었습니다.

이 포인터 주소라는 건 큰 데이터가 있는 것이 아니고 보통 정수로 된 데이터입니다. 그런데 이걸 일일히 클래스 명에 맞추어서 소스를 작성하는 건 조금 귀찮은 일이겠네요.

그래서 이 변수의 데이터 타입을 작성하기 쉽게 대체할 수 있는 키워드가 있는데 그것이 var 키워드입니다.

using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 프로퍼티
    public int Data
    {
      get; set;
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node 타입의 node 변수에 Node 인스턴스의 포인터를 넣는다.
      Node node = new Node();
      // 프로퍼티 변수 값 설정
      node.Data = 10;
      // 콘솔 출력
      Console.WriteLine(node.Data);
      
      // var 타입의 node1 변수에 Node 인스턴스의 포인터를 넣는다.
      var node1 = new Node();
      // 프로퍼티 변수 값 설정
      node1.Data = 20;
      // 콘솔 출력
      Console.WriteLine(node1.Data);
      
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제의 결과를 보면 node의 변수와 node1의 변수의 차이는 소스 상에서 작성만 Node의 클래스 타입에서 var로 바뀌었을 뿐, 프로그램 상에서 흘러가는 처리에 대해서는 똑같습니다.

즉, var 키워드란 변수에서 선언 간소화를 위한 키워드 일 뿐, 성능과 처리에 관해서는 아무런 영향이 없습니다.


그래서 C#의 소스 규약(코딩에 대한 규칙)에 따르면 로컬 변수 선언에는 var 키워드를 사용하는 것을 추천합니다.


그러나 이 var 키워드에는 몇가지 제약 조건이 있습니다.

최초 선언 시에 null은 선언할 수 없습니다.

그리고 최초 선언 시에 선언한 타입을 중간에 변경이 불가능합니다.

그리고 오로지 함수의 스택 영역, 즉 로컬에서만 사용이 가능하고 맴버 변수나 함수의 리턴값, 파라미터로는 사용할 수 없습니다.

정리하자면, var 키워드는 최초의 선언된 원시 데이터 혹은 클래스 타입으로 설정이 되고 그 다음에는 설정을 바꿀 수는 없습니다.

그리고 당연이 최초 선언에 타입을 설정하기 때문에 null은 안되고 맴버 변수와 반환 타입, 파라미터 타입으로는 설정을 구분할 수 없기 때문에 사용할 수 없습니다.


C#에는 var 키워드와 비슷한 dynamic 키워드가 있습니다.

dynamic 키워드는 var 키워드와 비슷하기는 합니다만, 최초 선언 시에 데이터 타입이 결정되는 것이 아니고 기본 데이터 타입인 Object로 설정이 됩니다.


Object 클래스에 대해서 간단하게 설명을 하면 C#에서는 가장 최소 단위는 클래스입니다.

그런데 이 클래스는 따로 상속을 설정하지 않아도 기본적으로 Object 클래스를 상속 받습니다. 즉, 가장 최상위 클래스라는 의미입니다.

Object는 조금 많이 복잡하기 때문에 다음 글에서 설명하겠습니다.


다시 dynamic으로 돌아와서 우리가 프로그램을 작성하다 보면 디버그의 오류로 인해 프로그램을 작성할 수 없는 경우가 있습니다.

using System;

namespace Example
{
  // 인터페이스
  interface INode
  {
  }
  // INode 인터페이스를 상속 받은 클래스
  class Node : INode
  {
    // 프로퍼티
    public int Data
    {
      get; set;
    }
  }
  class Program
  {
    // 함수로 인스턴스 취득
    static INode GetNode()
    {
      // 반환식
      return new Node();
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 변수의 타입은 INode로 설정하고, 함수로 인스턴스를 취득한다.
      INode node = GetNode();
      // Node 클래스에 Data 프로퍼티가 있기 떄문에 사용할 수 있습니다.
      node.Data = 1;
      // Node 클래스에 Data 프로퍼티 값을 취득한다.
      Console.WriteLine(node.Data);
      
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

그런데, Data를 참조할 수 없는 에러가 발생합니다.

이유는 GetNode함수에서 INode 타입으로 반환하고 또 변수 타입도 INode이기 때문에 INode에는 Data 프로퍼티가 없기 때문에 디버그 에러가 발생합니다.

뭐, 당연한 것입니다. 그렇다면 데이터 타입을 INode를 var로 바꾼다고 해서 Data를 사용할 수 있을 것 같지만, GetNode함수에서 INode로 반환을 하기 때문에 결국에는 같은 에러가 발생합니다.


그럼 위와 같은 식은 Data를 사용할 수 없을 것 같지만 dynamic 키워드를 사용하면 가능합니다.

using System;

namespace Example
{
  // 인터페이스
  interface INode
  {
  }
  // INode 인터페이스를 상속 받은 클래스
  class Node : INode
  {
    // 프로퍼티
    public int Data
    {
      get; set;
    }
  }
  class Program
  {
    // 함수로 인스턴스 취득
    static INode GetNode()
    {
      // 반환식
      return new Node();
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 변수의 타입은 dynamic로 설정하고, 함수로 인스턴스를 취득한다.
      dynamic node = GetNode();
      // Node 클래스의 Data를 참조한다.
      node.Data = 1;
      // Node 클래스의 Data 프로퍼티 값을 취득한다.
      Console.WriteLine(node.Data);
      
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

제대로 실행이 되네요.. dynamic 키워드는 일단 debug 환경에서의 에러는 잡아주지 않습니다.

그리고 실행을 하게 되면 실제 Heap 메모리에 들어가 있는 인스턴스의 함수와 변수를 찾아가기 때문에 실행이 되는 것입니다.


이 dynamic 키워드가 만능 같지만, 실제로는 이 dynamic은 Reflaction의 기능과 관련이 있는 키워드이기 때문에 일단 보통의 변수보다는 아주 약간 느립니다.

즉, 많이 사용하면 사용할 수록 성능이 느려질 수 있습니다.


그리고 에러를 debug상에서 잡아 주지 않기 때문에 프로그램의 품질과 관련이 있습니다.

프로그램을 작성하는 중에 에러를 알 수 없고 실행하는 중간에 에러가 발생하는 것이라 하나하나 실행과 테스트로 문제가 없다는 에러가 없다는 것을 검증해야 합니다.

using System;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Program의 인스턴스를 생성
      dynamic p = new Program();
      // Program 클래스는 Data 프로퍼티가 존재하지 않지만 디버그 에러가 발생하지 않습니다.
      p.Data = 1;
      
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

debug로 에러는 발생하지 않고 실행을 해야 에러가 발생하는 것을 확인할 수 있습니다.


dynamic 키워드는 var 키워드와 다르게 함수의 반환 타입으로 설정할 수 있고 맴버변수, 파라미터를 설정할 수 있습니다.

using System;

namespace Example
{
  class Program
  {
    // 맴버 변수
    private static dynamic data;
    // 함수의 반환식은 dynamic 타입과 파라미터의 dynamic 타입.
    public static dynamic Function(dynamic param)
    {
      // 반환의 값은 String 값이지만
      return data.ToString() + " : " + param.ToString();
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 맴버 변수 설정
      Program.data = "Dynamic Example";
      // 함수 실행
      var ret = Function("test");
      // 콘솔 출력
      Console.WriteLine(ret);
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보면 맴버 변수와 함수 반환값, 파라미터로 dynamic으로 사용할 수 있습니다.


여기까지 C#의 var 키워드와 dynamic 키워드에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.