Genius DM

C# > Tuples 본문

.NET

C# > Tuples

Damon Jung 2018. 7. 31. 02:50

Tuples


튜플에 대해서 알아보자




Tuples

System.Tuple 네임스페이스로 Key / Value 형태의 속성을 담을 수 있는 형태의 타입을 제공한다. 별도의 타입을 만들기는 싫은데 프로퍼티를 포함한 객체를 지닌 자료 구조를 만들고 싶을 때 사용할 수 있다. 아래 코드는 이름과 나이를 지닌 한 학생의 값을 반환 값으로 사용하는 예제이다.

public Tuple<string, int> GetStudentInfo(string id) { // Search by ID and find the student. return new Tuple<string, int>("Annie", 25); }


Tuple<string, int> 의 인스턴스를 반환하고 있는데, 첫 번째가 이름이고 두 번째 인자가 나이이다. 추후에 이 메서드를 호출하기 위해 아래와 같이 코드를 작성할 수 있다.


public void Test()
{     
    Tuple<string, int> info = GetStudentInfo("100-000-1000");
    Console.WriteLine($"Name: {info.Item1}, Age: {info.Item2}");
}


이름과 나이의 값을 Item1 과 Item2 를 참조하여 얻을 수 있다.




튜플 객체는 몇 가지 문제점을 지니고 있다.

    • 속성에 접근할 때 특정 네임 패턴 ( ItemX ) 를 사용해야 하고 속성 명칭이 호출자에겐 이상해보일 수 있다. info.Name 이나 info.Age 로 쓰는 것이 info.Item1 이나 info.Item2 로 쓰는 것 보다 확실히 나아보인다.

    • 속성은 최대 8개 까지만 제한되어 있으며, 더 필요한 경우 마지막 속성을 또 다른 Tuple 로 지정할 수 있다. 당연히 이렇게 쓰면 가독성이 매우 떨어진다.

    • 튜플은 참조 타입이며 다른 원시 타입 ( 대부분 벨류 타입 ) 과는 다르게 Heap 메모리에 할당되고 CPU 가 많이 사용되는 작업에서 객체 생성/할당이 지나치게 일어날 수 있다. 




벨류 타입 튜플

C# 7.0 에서 ValueTuple 이 소개되었는데, 튜플 객체를 벨류 타입으로 표현한 것이다. C# 언어 팀이 이 벨류 타입 튜플에 훌륭한 기능을 많이 넣어놔서, 새로운 문법과 다양한 기능 ( 소멸자 같은 ) 이 추가되었다.

아래는 벨류 타입 튜플을 이용해 재 작성한 코드이다. 프로젝트에서 ValueTuple 이 보이지 않으면 System.ValueTuple 4.3.0 NuGet package 를 다운받아야 한다. .NET 4.7 또는 상위 버전을 사용하거나 .NET standard Library 2.0 이상을 사용한다면 따로 다운받을 필요 없다.

public (string, int) GetStudentInfo(string id)
{
    // Search by ID and find the student. 
    return ("Annie", 25);
} 

public void Test()
{ 
    (string, int) info = GetStudentInfo("100-000-1000"); 
    Console.WriteLine($"Name: {info.Item1}, Age: {info.Item2}");
}


위 코드는 (string, int) 같은 괄호 문법을 이용하여 훨씬 더 간결해졌다. 심지어 벨류 튜플에서 모든 요소에 이름을 지정할 수도 있다. 아래를 보자.


public (string name, int age) GetStudentInfo(string id) { // Search by ID and find the student. return (name: "Annie", age: 25); } public void Test() { (string name, int age) info = GetStudentInfo("100-000-1000"); Console.WriteLine($"Name: {info.name}, Age: {info.age}"); }


완벽하다! 이제 튜플 객체에 요소들에 대한 메타데이터를 지니게 됐다. 이로써 최초 정의된 올바른 순서대로 요소들에 접근하고 리턴받기 위해 왔다갔다 할 필요가 없어졌다.


VisualStudio 가 벨류 튜플로 작업할 때 아래 처럼 인텔리센스를 제공할 것이다.


image


image





벨류 튜플 소멸자

벨류 튜플 객체에서 요소들을 소멸시킬 수 있고 로컬 변수에 접근할 수 있다.

// Deconstruct using the var (x, y) syntax,
// or (var x, var y) syntax.
var (name, age) = GetStudentInfo("100-000-1000");

// Now you have two local variables: name and age.
Console.WriteLine($"Name: {name}, Age: {age}");

// If you just care about certain elements but not all, you can use the _ keyword to ignore the local variable.


// Deconstruct using the var (x, y) syntax,
// or (var x, var y) syntax.
var (name, _) = GetStudentInfo("100-000-1000");


// Now you have just one local variable: name. The value for age is ignored.
Console.WriteLine($"Name: {name}");





벨류 튜플을 튜플로

System.Tuple 과 System.ValueTuple 은 몇 가지 확장 메서드를 제공해서 Tuple 과 ValueTuple 사이에 변환을 할 수 있게 해준다.

var valueTuple = (id: 1, name: "Annie", age: 25, dob: DateTime.Parse("1/1/1993"));
var tuple = valueTuple.ToTuple();





결론

ValueTuple 은 C# 을 더욱 모던하게 해주며 간결한 문법으로 사용하기도 쉽다. 또 기존 튜플 문제를 여러가지 해결해주었다.

    • ValueTuple 객체는 튜플 요소에 대한 작업을 간결하게 만들어주었고 First class syntax 를 제공 해준다.

    • 이름을 벨류 튜플 요소와 연관지을 수도 있고 코드를 어느 정도 수준에서 컴파일 타임 / 디자인 타임 유효성 검사를 해주기도 한다.

    • 튜플 요소와 관련된 이름은 런타임 메타데이터가 아니라는 사실을 기억하자. 이를테면 실제 벨류 튜플 인스턴스에는 이름이 부여된 프로퍼티나 필드가 존재하지 않는다. 프로퍼티 명은 여전히 Item1, Item2 이며, 디자인 타임 / 컴파일 타임에서만 이름이 존재한다.

    • _ 키워드와 소멸자를 이용하여 모든 튜플의 요소들을 유연하게 접근할 수 있다. ValueTuple 타입은 벨류 타입이며 상속이나 다른 기능은 제공되지 않는다. 즉 벨류 튜플이 성능이 좋다는 이야기이다.

    • 튜플 요소의 이름 값 자체는 런타임 벨류가 아니기 때문에 JSON.NET 같은 라이브러리를 통해 직렬화 할 때 조심해야 한다. 해당 라이브러리가 ValueTuple 의 새로운 메타데이터를 ( TupleElementNameAttribute ) 지원하지 않는 이상 주의를 기울여야 한다. 



위 글과 그림은 https://blogs.msdn.microsoft.com/mazhou/2017/05/26/c-7-series-part-1-value-tuples/ 에서 발췌, 번역하였습니다.



















Tuples


Post over Tuple object.




Tuples

The System.Tuple class provides a type to represent a key-value like property bag. It can be used where you want to have a data structure to hold an object with properties (elements) but you don’t want to create a separate type for it. The following code shows how you use it as a return value of a method that holds the name and the age of a student.

public Tuple<string, int> GetStudentInfo(string id) { // Search by ID and find the student. return new Tuple<string, int>("Annie", 25); }


As you can see, I am returning an instance of Tuple<string, int> object with first argument is the name and the second argument is the age. Later on we can have code to call this method, like this:


public void Test()
{     
    Tuple<string, int> info = GetStudentInfo("100-000-1000");
    Console.WriteLine($"Name: {info.Item1}, Age: {info.Item2}");
}


You can access name and age by referencing Item1 and Item2.




The Tuple class has some obvious problems:

    • You need to access the properties with a name pattern ItemX, the property name may not make sense to the caller, that would be better if we can write something like info.Name and info.Age instead of info.;Item1 and info.Item2. 

    • You are limited to have maximum 8 properties. If you want more, the last type argument must be another Tuple. This makes the syntax super hard to understand.

    • Tuple is a reference type, not like other primitive types (they are most value types), it allocates on heap and it could be too much object creation/allocation for CPU intensive operations.




Value Tuples

C# 7.0 introduced ValueTuple structure, which is a value type representation of the tuple object. The language team made many good things for this value tuple type, including a new syntax and many features (such as deconstruction.)

The following is a rewrite version with the value tuples, Note that if you don’t see the ValueTuple available in your project, you have to download the System.ValueTuple 4.3.0 NuGet package to your project. You don’t need to do anything if you are using .NET Framework 4.7 or higher, or .NET Standard Library 2.0 or higher.

public (string, int) GetStudentInfo(string id)
{
    // Search by ID and find the student. 
    return ("Annie", 25);
} 

public void Test()
{ 
    (string, int) info = GetStudentInfo("100-000-1000"); 
    Console.WriteLine($"Name: {info.Item1}, Age: {info.Item2}");
}


The code above is much simplified by using the first-class syntax (). You can even give a name to every element in the value tuple, like this:


public (string name, int age) GetStudentInfo(string id) { // Search by ID and find the student. return (name: "Annie", age: 25); } public void Test() { (string name, int age) info = GetStudentInfo("100-000-1000"); Console.WriteLine($"Name: {info.name}, Age: {info.age}"); }


Perfect! Now you have good metadata for the elements in tuple objects, you then don’t need to go back and forth to make sure you are returning/accessing elements in the right order as its original definition.


The Visual Studio IDE will give you hints when you work with the value tuples.


image


image





ValueTuple Deconstruction

You can deconstruct the elements from the value tuple object, and access the local variables.

// Deconstruct using the var (x, y) syntax,
// or (var x, var y) syntax.
var (name, age) = GetStudentInfo("100-000-1000");

// Now you have two local variables: name and age.
Console.WriteLine($"Name: {name}, Age: {age}");

// If you just care about certain elements but not all, you can use the _ keyword to ignore the local variable.


// Deconstruct using the var (x, y) syntax,
// or (var x, var y) syntax.
var (name, _) = GetStudentInfo("100-000-1000");


// Now you have just one local variable: name. The value for age is ignored.
Console.WriteLine($"Name: {name}");





ValueTuples to Tuples

The System.Tuple and System.ValueTuple provides some extension methods to help convert between Tuple and ValueTuple.

var valueTuple = (id: 1, name: "Annie", age: 25, dob: DateTime.Parse("1/1/1993"));
var tuple = valueTuple.ToTuple();





Conclusion

ValueTuple makes the C# language more modern, and easy to use with simplified syntax. It solves many Tuple problems: 

    • Value Tuple objects have first class syntax support, it simplifies the code to work with tuple elements. You can associate a name with the value tuple element, you get some level of design time and compiler time validation of your code. 

    • Please NOTE that the name associated with the tuple element is not a runtime metadata, i.e. there is no such a property/field with the name on the actual instance of that value tuple object, the property names are still Item1, Item2, etc., all element names are design time and compiler time only.

    • You are now flexible to access all tuple elements, or some of them, by using the deconstruction and the _ keyword.

    • Value Tuple types are value types, no inheritance or other features, this means that the value tuples are better performance.

    • Since the name of the value tuple element is not runtime, you have to be careful using it when doing serialization with existing libraries, such as Newtonsoft.Json, Unless the library updated to support the new metadata (TupleElementNameAttribute etc.) before then you would run into bugs.




Comments