System.ValueTuple과 System.Tuple의 차이점은 무엇입니까?


139

일부 C # 7 라이브러리를 디 컴파일하고 ValueTuple제네릭이 사용되는 것을 보았습니다 . 대신 무엇 ValueTuples이며 왜 그렇지 Tuple않습니까?


Dot NEt Tuple 클래스를 언급한다고 생각합니다. 샘플 코드를 공유해 주시겠습니까? 이해하기 쉽도록
Ranadip Dutta

14
@Ranadip Dutta : 튜플이 무엇인지 안다면 질문을 이해하기 위해 샘플 코드가 필요하지 않습니다. ValueTuple은 무엇이며 Tuple과 어떻게 다른가요?
BoltClock

1
@ BoltClock : 그 맥락에서 아무것도 대답하지 않은 이유입니다. 나는 C #에서 알고 있는데, Tuple 클래스는 꽤 ​​자주 사용되며 때로는 같은 클래스를 powershell에서 호출합니다. 참조 유형입니다. 이제 다른 답변을 보면서 Valuetuple이라고도하는 값 유형이 있음을 이해했습니다. 샘플이 있으면 사용법을 알고 싶습니다.
Ranadip Dutta

2
Roslyn의 소스 코드가 github에서 사용 가능할 때 왜 이것을 디 컴파일합니까?
Zein Makki

@ user3185569는 F12 일을 자동-컴파일 해제 탓하고 쉽게 점프보다 더 GitHub의에
존 Zabroski

답변:


203

대신 무엇 ValueTuples이며 왜 그렇지 Tuple않습니까?

A ValueTuple는 원래 System.Tuple클래스 와 같은 튜플을 반영하는 구조체입니다 .

와의 주요 차이점은 다음 TupleValueTuple같습니다.

  • System.ValueTuple값 유형 (struct)이고 System.Tuple참조 유형 ( class)입니다. 이는 할당 및 GC 압력에 대해 이야기 할 때 의미가 있습니다.
  • System.ValueTuple뿐만 아니라이 struct, 그것의 가변 , 다른 하나는로를 사용할 때주의해야한다. 수업 System.ValueTuple에서 필드를 가질 때 어떤 일이 발생하는지 생각해보십시오 .
  • System.ValueTuple 속성 대신 필드를 통해 항목을 노출합니다.

C # 7까지는 튜플을 사용하는 것이 그리 편리하지 않았습니다. 필드 이름은 Item1, Item2등이며 언어는 다른 대부분의 언어 (Python, Scala)와 마찬가지로 구문 설탕을 제공하지 않았습니다.

.NET 언어 디자인 팀이 언어 수준에서 튜플을 통합하고 구문 설탕을 추가하기로 결정했을 때 중요한 요소는 성능이었습니다. 함께 ValueTuple있기 때문에 (구현 세부로)를 사용할 때 값 형식 인, 당신은 그들이 스택에 할당됩니다 GC 압력을 피할 수 있습니다.

또한 struct런타임에 의해 자동 (얕은) 평등 의미론을 얻습니다 class. 디자인 팀은 튜플에 대해 더욱 최적화 된 평등이 보장되도록했지만이를 위해 커스텀 평등을 구현했습니다.

다음은 디자인 노트의Tuples 단락 입니다 .

구조 또는 클래스 :

언급했듯이, 할당 형벌과 관련이 없도록 튜플 유형을 만드는 structs것이 좋습니다 classes. 가능한 한 가벼워 야합니다.

틀림없이, structs때문에 할당 사본 더 큰 가치, 비용이 많이 드는 끝나게 수 있습니다. 따라서 생성 된 것보다 더 많은 것이 할당되면 structs잘못된 선택이됩니다.

그러나 동기 부여에있어 튜플은 일시적입니다. 부품이 전체보다 중요 할 때 사용합니다. 따라서 일반적인 패턴은 구성, 반환 및 즉시 해체하는 것입니다. 이 상황에서 구조체가 분명히 바람직합니다.

Structs에는 여러 가지 다른 이점이 있으며 다음과 같은 이점이 있습니다.

예 :

작업 System.Tuple이 매우 빨리 모호해진다는 것을 쉽게 알 수 있습니다 . 예를 들어, 합계와 개수를 계산하는 방법이 있다고 가정 해보십시오 List<Int>.

public Tuple<int, int> DoStuff(IEnumerable<int> values)
{
    var sum = 0;
    var count = 0;

    foreach (var value in values) { sum += value; count++; }

    return new Tuple(sum, count);
}

수신 측에서는 다음과 같이 끝납니다.

Tuple<int, int> result = DoStuff(Enumerable.Range(0, 10));

// What is Item1 and what is Item2?
// Which one is the sum and which is the count?
Console.WriteLine(result.Item1);
Console.WriteLine(result.Item2);

값 튜플을 명명 된 인수로 해체 할 수있는 방법은이 기능의 진정한 힘입니다.

public (int sum, int count) DoStuff(IEnumerable<int> values) 
{
    var res = (sum: 0, count: 0);
    foreach (var value in values) { res.sum += value; res.count++; }
    return res;
}

그리고받는 쪽에서 :

var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");

또는:

var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");

컴파일러 장점 :

이전 예제의 커버를 살펴보면 컴파일러가 ValueTuple해체하도록 요청할 때 컴파일러가 어떻게 해석하는지 정확하게 알 수 있습니다 .

[return: TupleElementNames(new string[] {
    "sum",
    "count"
})]
public ValueTuple<int, int> DoStuff(IEnumerable<int> values)
{
    ValueTuple<int, int> result;
    result..ctor(0, 0);
    foreach (int current in values)
    {
        result.Item1 += current;
        result.Item2++;
    }
    return result;
}

public void Foo()
{
    ValueTuple<int, int> expr_0E = this.DoStuff(Enumerable.Range(0, 10));
    int item = expr_0E.Item1;
    int arg_1A_0 = expr_0E.Item2;
}

내부적으로 컴파일 된 코드 분류기는 Item1그리고 Item2우리는 분해 튜플와 함께 작동하기 때문에,하지만이 모든 것은 우리에게서 멀리 추상화된다. 명명 된 인수를 가진 튜플에는로 주석이 달립니다 TupleElementNamesAttribute. 분해하는 대신 하나의 새로운 변수를 사용하면 다음과 같은 결과를 얻습니다.

public void Foo()
{
    ValueTuple<int, int> valueTuple = this.DoStuff(Enumerable.Range(0, 10));
    Console.WriteLine(string.Format("Sum: {0}, Count: {1})", valueTuple.Item1, valueTuple.Item2));
}

컴파일러는 여전히 우리가 우리의 응용 프로그램을 디버깅 할 때 볼 이상한 것 같은 약간의 마법 (속성을 통해) 일어날 수 있도록해야합니다 Item1, Item2.


1
당신은 또한 더 간단한 (그리고 내 의견으로는, 선호하는) 구문을 사용할 수 있습니다.var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Abion47

@ Abion47 두 유형이 다를 경우 어떻게됩니까?
유발 이츠 차 코프

또한 " 변경 가능한 구조체입니다"와 " 읽기 전용 필드를 노출합니다"라는 점이 어떻게 동의합니까?
코드 InChaos

@CodesInChaos 그렇지 않습니다. [this] ( github.com/dotnet/corefx/blob/master/src/Common/src/System/… )를 보았지만 로컬 필드를 사용할 수 없기 때문에 컴파일러에서 방출되는 것으로 생각하지 않습니다. 어쨌든 읽기 전용. 나는 그 제안이 "원한다면 읽기 전용으로 만들 수 있지만 그것이 당신에게 달려있다"는 의미라고 생각한다 .
유발 이츠 차 코프

1
일부 니트 : "그들은 스택에 할당됩니다" -지역 변수에만 해당됩니다. 의심 할 여지없이 이것을 안타깝게도,이를 표현한 방식은 항상 가치 유형이 항상 스택에 존재한다는 신화를 영속시킬 것입니다.
Peter Duniho

26

차이 TupleValueTupleTuple기준 유형 및 ValueTuple값 유형이다. 후자는 C # 7의 언어 변경에 튜플이 훨씬 더 자주 사용되기 때문에 바람직하지만 모든 튜플에 대해 힙에 새 객체를 할당하는 것은 특히 불필요한 경우 성능 문제입니다.

그러나 C # 7에서는 튜플 사용을 위해 추가 된 구문 설탕으로 인해 두 유형을 명시 적으로 사용할 필요 가 없습니다 . 예를 들어, C # 6에서 튜플을 사용하여 값을 반환하려면 다음을 수행해야합니다.

public Tuple<string, int> GetValues()
{
    // ...
    return new Tuple(stringVal, intVal);
}

var value = GetValues();
string s = value.Item1; 

그러나 C # 7에서는 다음을 사용할 수 있습니다.

public (string, int) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var value = GetValues();
string s = value.Item1; 

한 단계 더 나아가 값 이름을 지정할 수도 있습니다.

public (string S, int I) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var value = GetValues();
string s = value.S; 

... 또는 튜플을 완전히 해체하십시오.

public (string S, int I) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var (S, I) = GetValues();
string s = S;

튜플은 성 가시고 장황하기 때문에 C # pre-7에서 자주 사용되지 않았으며 단일 작업 인스턴스에 대한 데이터 클래스 / 구조체를 작성하는 것이 가치가있는 것보다 더 문제가되는 경우에만 실제로 사용되었습니다. 그러나 C # 7에서 튜플은 현재 언어 수준을 지원하므로이를 사용하는 것이 훨씬 깨끗하고 유용합니다.


10

Tuple와 에 대한 소스를 살펴 보았습니다 ValueTuple. 차이점은 즉 TupleA는 class하고 ValueTupleA는 struct그 구현 IEquatable.

즉, Tuple == Tuple반환 false그들은 같은 인스턴스 아니지만, 경우에 ValueTuple == ValueTuple반환됩니다 true그들이 동일한 유형 및의 경우 Equals반환 true에 포함 된 각 값에 대한이.


그래도 그 이상입니다.
BoltClock

2
@BoltClock 당신이 당신의 의견을 정교하게 만들면 건설적인 것입니다
피터 모리스

3
또한 값 유형이 반드시 스택에있는 것은 아닙니다. 차이점은 변수가 저장 될 때마다 스택이 될 수도 있고 아닐 수도있는 참조가 아닌 의미를 의미 적으로 나타냅니다.
Servy

6

다른 답변은 중요한 점을 언급하는 것을 잊었습니다. rephrasing 대신 소스 코드 에서 XML 문서를 참조하겠습니다 .

ValueTuple 유형 (arity 0에서 8까지)은 C #의 튜플과 F #의 튜플을 기본으로하는 런타임 구현을 구성합니다.

언어 구문을 통해 생성 된 것 외에도ValueTuple.Create 팩토리 메소드 를 통해 가장 쉽게 생성됩니다 . System.ValueTuple유형은 다음과 같은 유형과 다릅니다 System.Tuple.

  • 그들은 수업이 아닌 구조체입니다.
  • 그들은 오히려 읽기 전용보다는 변경할 수 있습니다 , 그리고
  • 멤버 (Item1, Item2 등)는 속성이 아닌 필드입니다.

이 유형과 C # 7.0 컴파일러를 도입하면 쉽게 작성할 수 있습니다.

(int, string) idAndName = (1, "John");

그리고 메소드에서 두 개의 값을 반환합니다 :

private (int, string) GetIdAndName()
{
   //.....
   return (id, name);
}

반대로 System.Tuple의미있는 이름을 지정할 수있는 공개 읽기 / 쓰기 필드이기 때문에 멤버 (Mutable)를 업데이트 할 수 있습니다.

(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";

"군단 0 ~ 8". 아, 나는 그들이 0 튜플을 포함한다는 사실을 좋아합니다. 이것은 일종의 빈 타입으로 사용될 수 있으며, class MyNonGenericType : MyGenericType<string, ValueTuple, int>등등 과 같은 타입 파라미터가 필요하지 않을 때 제네릭에서 허용 될 것입니다 .
Jeppe Stig Nielsen

6

위의 주석 외에도 ValueTuple의 불행한 단점 중 하나는 값 유형으로 명명 된 인수가 IL로 컴파일 될 때 지워 지므로 런타임시 직렬화에 사용할 수 없다는 것입니다.

즉, 예를 들어 Json.NET을 통해 직렬화하면 달콤한 명명 된 인수가 여전히 "Item1", "Item2"등으로 나타납니다.


2
기술적으로는 비슷하지만 차이는 없습니다.)
JAD

2

이 두 가지 사실에 대한 빠른 설명을 추가하기 위해 늦게 참여했습니다.

  • 그들은 수업이 아닌 구조체입니다
  • 그들은 읽기 전용이 아니라 변경 가능하다

가치 튜플 en-masse를 변경하는 것이 간단하다고 생각할 것입니다.

 foreach (var x in listOfValueTuples) { x.Foo = 103; } // wont even compile because x is a value (struct) not a variable

 var d = listOfValueTuples[0].Foo;

누군가 다음과 같이이 문제를 해결하려고 시도 할 수 있습니다.

 // initially *.Foo = 10 for all items
 listOfValueTuples.Select(x => x.Foo = 103);

 var d = listOfValueTuples[0].Foo; // 'd' should be 103 right? wrong! it is '10'

이 기발한 동작의 이유는 값 튜플이 정확히 값 기반 (구조)이므로 .Select (...) 호출은 원본이 아닌 복제 된 구조에서 작동하기 때문입니다. 이 문제를 해결하려면 다음을 수행해야합니다.

 // initially *.Foo = 10 for all items
 listOfValueTuples = listOfValueTuples
     .Select(x => {
         x.Foo = 103;
         return x;
     })
     .ToList();

 var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed

또는 물론 간단한 접근 방식을 시도 할 수 있습니다.

   for (var i = 0; i < listOfValueTuples.Length; i++) {
        listOfValueTuples[i].Foo = 103; //this works just fine

        // another alternative approach:
        //
        // var x = listOfValueTuples[i];
        // x.Foo = 103;
        // listOfValueTuples[i] = x; //<-- vital for this alternative approach to work   if you omit this changes wont be saved to the original list
   }

   var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed

이것이 목록 호스팅 값 튜플에서 꼬리를 만들기 위해 고군분투하는 데 도움이되기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.