일부 C # 7 라이브러리를 디 컴파일하고 ValueTuple
제네릭이 사용되는 것을 보았습니다 . 대신 무엇 ValueTuples
이며 왜 그렇지 Tuple
않습니까?
일부 C # 7 라이브러리를 디 컴파일하고 ValueTuple
제네릭이 사용되는 것을 보았습니다 . 대신 무엇 ValueTuples
이며 왜 그렇지 Tuple
않습니까?
답변:
대신 무엇
ValueTuples
이며 왜 그렇지Tuple
않습니까?
A ValueTuple
는 원래 System.Tuple
클래스 와 같은 튜플을 반영하는 구조체입니다 .
와의 주요 차이점은 다음 Tuple
과 ValueTuple
같습니다.
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
.
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
차이 Tuple
와 ValueTuple
즉 Tuple
기준 유형 및 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에서 튜플은 현재 언어 수준을 지원하므로이를 사용하는 것이 훨씬 깨끗하고 유용합니다.
Tuple
와 에 대한 소스를 살펴 보았습니다 ValueTuple
. 차이점은 즉 Tuple
A는 class
하고 ValueTuple
A는 struct
그 구현 IEquatable
.
즉, Tuple == Tuple
반환 false
그들은 같은 인스턴스 아니지만, 경우에 ValueTuple == ValueTuple
반환됩니다 true
그들이 동일한 유형 및의 경우 Equals
반환 true
에 포함 된 각 값에 대한이.
다른 답변은 중요한 점을 언급하는 것을 잊었습니다. rephrasing 대신 소스 코드 에서 XML 문서를 참조하겠습니다 .
ValueTuple 유형 (arity 0에서 8까지)은 C #의 튜플과 F #의 튜플을 기본으로하는 런타임 구현을 구성합니다.
언어 구문을 통해 생성 된 것 외에도ValueTuple.Create
팩토리 메소드 를 통해 가장 쉽게 생성됩니다
. System.ValueTuple
유형은 다음과 같은 유형과 다릅니다 System.Tuple
.
이 유형과 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";
class MyNonGenericType : MyGenericType<string, ValueTuple, int>
등등 과 같은 타입 파라미터가 필요하지 않을 때 제네릭에서 허용 될 것입니다 .
이 두 가지 사실에 대한 빠른 설명을 추가하기 위해 늦게 참여했습니다.
가치 튜플 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
이것이 목록 호스팅 값 튜플에서 꼬리를 만들기 위해 고군분투하는 데 도움이되기를 바랍니다.