Bazı C # 7 kütüphanelerini deşifre ettim ve ValueTuple
jeneriklerin kullanıldığını gördüm . Ne var ValueTuples
ve neden olmasın Tuple
?
Bazı C # 7 kütüphanelerini deşifre ettim ve ValueTuple
jeneriklerin kullanıldığını gördüm . Ne var ValueTuples
ve neden olmasın Tuple
?
Yanıtlar:
Ne var
ValueTuples
ve neden olmasınTuple
?
A ValueTuple
, orijinal System.Tuple
sınıfla aynı bir tupu yansıtan bir yapıdır .
Arasındaki temel fark Tuple
ve ValueTuple
şunlardır:
System.ValueTuple
bir değer tipidir (yapı), System.Tuple
bir referans tipidir ( class
). Bu, tahsisler ve GC basıncı hakkında konuşurken anlamlıdır.System.ValueTuple
sadece bir değil struct
, değişebilir ve onları bu şekilde kullanırken dikkatli olmak gerekiyor. Sınıf a'yı System.ValueTuple
alan olarak tuttuğunda ne olacağını düşünün .System.ValueTuple
öğelerini özellikler yerine alanlar aracılığıyla gösterir.C # 7'ye kadar, tuples kullanmak çok uygun değildi. Onların saha isimler Item1
, Item2
çoğu diğer diller gibi onlara vb ve dil verilen olmasaydı sözdizimi şeker (Python, Scala) yapmak.
.NET dil tasarım ekibi, dil düzeyinde tuples kullanmaya ve sözdizimi şekeri eklemeye karar verdiğinde önemli bir faktör performanstı. İle ValueTuple
çünkü (bir uygulama ayrıntı olarak) bunları kullanırken bir değer türü olmanın, onların yığını üzerinde tahsis olacağım GC basıncı önleyebilirsiniz.
Ayrıca, struct
çalışma zamanı tarafından otomatik (sığ) eşitlik anlambilimi alır, burada bir class
değil. Tasarım ekibi, tuples için daha da optimize edilmiş bir eşitlik olacağından emin olsa da, bunun için özel bir eşitlik uyguladı.
İşte tasarım notlarındanTuples
bir paragraf :
Yapı veya Sınıf:
Bahsettiğim gibi, onlardan
structs
ziyade tuple türleri yapmayı öneriyorumclasses
, böylece hiçbir tahsis cezası ilişkili değil. Mümkün olduğunca hafif olmalıdırlar.Muhtemelen,
structs
daha pahalı olabilir, çünkü atama daha büyük bir değer kopyalar. Dolayısıyla, yaratıldıklarından çok daha fazla atanırlarsa, ostructs
zaman kötü bir seçim olacaktır.Yine de onların motivasyonlarında, tupllar geçici. Parçalar bütünden daha önemli olduğunda bunları kullanabilirsiniz. Böylece ortak örüntü onları inşa etmek, geri döndürmek ve derhal yapısöktürmek olacaktır. Bu durumda yapılar açıkça tercih edilir.
Yapılar ayrıca, aşağıda açıklanacak olan başka faydalara da sahiptir.
System.Tuple
Birlikte çalışmanın çok hızlı bir şekilde belirsiz hale geldiğini kolayca görebilirsiniz . Örneğin, toplamı ve a'nın sayısını hesaplayan bir yöntemimiz olduğunu varsayalım 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);
}
Alıcı ucunda:
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);
Değer gruplarını adlandırılmış bağımsız değişkenlere göre yapılandırabilmenin yolu, özelliğin gerçek gücüdür:
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;
}
Ve alıcı tarafta:
var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");
Veya:
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");
Önceki örneğimizin kapağının altına bakarsak, derleyiciyi yapısökmesini istediğimizde tam olarak nasıl yorumladığını görebiliriz 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;
}
Dahili olarak, derlenmiş kod kullanır Item1
ve Item2
ayrıştırılmış bir demet ile çalıştığımızdan, bunların hepsi bizden soyutlanır. Adlı bağımsız değişkenlere sahip bir demet ek açıklama ile eklenir TupleElementNamesAttribute
. Ayrıştırma yerine tek bir taze değişken kullanırsak, şunu elde ederiz:
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));
}
Derleyici hala bizim uygulama hata o görmek garip olurdu gibi bazı büyü, (özelliğiyle) gerçekleşmesi için sahip olduğu Not Item1
, Item2
.
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Arasındaki fark Tuple
ve ValueTuple
olduğunu Tuple
başvuru türü ve ValueTuple
bir değer türüdür. İkincisi arzu edilir, çünkü C # 7'deki dilde yapılan değişiklikler çok daha sık tupllere sahiptir, ancak her bir demet için yığın üzerine yeni bir nesne tahsis etmek, özellikle gereksiz olduğunda bir performans endişesidir.
Ancak, C # 7, fikri asla olmasıdır sahip açıkça çünkü tanımlama grubu kullanılmak üzere eklenen sözdizimi şeker ya tipini kullanmak. Örneğin, C # 6'da, bir değer döndürmek için bir demet kullanmak isterseniz, aşağıdakileri yapmanız gerekir:
public Tuple<string, int> GetValues()
{
// ...
return new Tuple(stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
Ancak, C # 7'de bunu kullanabilirsiniz:
public (string, int) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.Item1;
Hatta bir adım daha ileri gidebilir ve değerlerin adlarını verebilirsiniz:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var value = GetValues();
string s = value.S;
... Veya demeti tamamen bozun:
public (string S, int I) GetValues()
{
// ...
return (stringVal, intVal);
}
var (S, I) = GetValues();
string s = S;
Tuples C # 7'de sık sık kullanılmadıkları için hantal ve ayrıntılıydılar ve sadece tek bir çalışma örneği için bir veri sınıfı / yapı oluşturmanın değerinden daha fazla sorun olacağı durumlarda gerçekten kullanıldılar. Ancak C # 7'de, tuples'ların artık dil düzeyinde desteği var, bu yüzden bunları kullanmak çok daha temiz ve daha kullanışlı.
Her ikisinin de kaynağına baktım Tuple
ve ValueTuple
. Fark şu ki Tuple
, bir class
ve ValueTuple
bir struct
uygulayan IEquatable
.
Aracının Tuple == Tuple
döner false
aynı örneği değildir, ancak eğer ValueTuple == ValueTuple
döndürür true
aynı tür ve eğer Equals
geri dönüş true
içerdikleri değerlerin her biri için.
Diğer cevaplar önemli noktalardan bahsetmeyi unuttu.Yeniden ifade etmek yerine, XML belgelerine kaynak koddan referans vereceğim :
ValueTuple türleri (0 - 8 arası değişkenlik), C # 'da tuples ve F #' da tuples altında yatan çalışma zamanı uygulamasını içerir.
Dil sözdizimi ile oluşturulmuş olmasının yanı sıra , en kolay ValueTuple.Create
fabrika yöntemleri ile oluşturulur
. System.ValueTuple
Tipleri farklı System.Tuple
ki türleri:
Bu tip ve C # 7.0 derleyicisinin tanıtımı ile kolayca yazabilirsiniz.
(int, string) idAndName = (1, "John");
Ve bir yöntemden iki değer döndürün:
private (int, string) GetIdAndName()
{
//.....
return (id, name);
}
Bunun aksine System.Tuple
, üyelerini (Mutable) güncelleyebilirsiniz, çünkü bunlar anlamlı adlar verilebilen herkese açık okuma-yazma Alanlarıdır:
(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
class MyNonGenericType : MyGenericType<string, ValueTuple, int>
vb. Gibi bazı tip parametrelerine ihtiyaç duyulmadığında jeneriklere izin verilir
Yukarıdaki yorumlara ek olarak, ValueTuple'in talihsiz bir durumu, bir değer türü olarak, IL'ye derlendiğinde adlandırılmış argümanların silinmesi, böylece çalışma zamanında serileştirme için mevcut olmamalarıdır.
yani tatlı adlandırılmış argümanlarınız, örneğin Json.NET yoluyla serileştirildiğinde yine de "Item1", "Item2" vb.
Bu iki factoid hakkında hızlı bir açıklama eklemek için geç katılım:
Kişi, değer gruplarını değiştirmenin doğrudan olacağını düşünürdü:
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;
Birisi bu şekilde geçici bir çözüm bulmaya çalışabilir:
// 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'
Bu ilginç davranışın nedeni, değer gruplarının tam olarak değere dayalı (yapı) olması ve bu nedenle .Select (...) çağrısının, orijinaller yerine klonlanmış yapılarda çalışmasıdır. Bunu çözmek için şunlara başvurmalıyız:
// 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
Alternatif olarak, elbette basit bir yaklaşım denenebilir:
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
Umarım bu, listeye barındırılan değer gruplarından kuyruk başı yapmak için mücadele eden birine yardımcı olur.