System.ValueTuple ve System.Tuple arasındaki fark nedir?


139

Bazı C # 7 kütüphanelerini deşifre ettim ve ValueTuplejeneriklerin kullanıldığını gördüm . Ne var ValueTuplesve neden olmasın Tuple?


Bence Dot NEt Tuple sınıfına atıfta bulunuyor. Lütfen bir örnek kod paylaşır mısınız? Böylece anlaşılması kolay olur.
Ranadip Dutta

14
@Ranadip Dutta: Bir grubun ne olduğunu biliyorsanız, soruyu anlamak için örnek kod gerekmez. Sorunun kendisi açıktır: ValueTuple nedir ve Tuple'dan farkı nedir?
BoltClock

1
@BoltClock: Bu bağlamda hiçbir şeye cevap vermememin nedeni budur. C #, ben oldukça sık kullandığım bir Tuple sınıf var ve aynı sınıf powershell de denir bazen. Bu bir referans türüdür. Şimdi diğer cevapları görünce Valuetuple olarak da bilinen bir değer türü olduğunu anladım. Bir örnek varsa, bunun kullanımını bilmek istiyorum.
Ranadip Dutta

2
Roslyn'in kaynak kodu github'da mevcut olduğunda bunları neden kaynaştırmıyorsunuz?
Zein Mekkî

@ user3185569 muhtemelen F12 şeyleri otomatik olarak ayrıştırır ve
GitHub'a

Yanıtlar:


203

Ne var ValueTuplesve neden olmasın Tuple?

A ValueTuple, orijinal System.Tuplesınıfla aynı bir tupu yansıtan bir yapıdır .

Arasındaki temel fark Tupleve ValueTupleşunlardır:

  • System.ValueTuplebir değer tipidir (yapı), System.Tuplebir referans tipidir ( class). Bu, tahsisler ve GC basıncı hakkında konuşurken anlamlıdır.
  • System.ValueTuplesadece bir değil struct, değişebilir ve onları bu şekilde kullanırken dikkatli olmak gerekiyor. Sınıf a'yı System.ValueTuplealan 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 classdeğ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 structsziyade tuple türleri yapmayı öneriyorum classes, böylece hiçbir tahsis cezası ilişkili değil. Mümkün olduğunca hafif olmalıdırlar.

Muhtemelen, structsdaha pahalı olabilir, çünkü atama daha büyük bir değer kopyalar. Dolayısıyla, yaratıldıklarından çok daha fazla atanırlarsa, o structszaman 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.

Örnekler:

System.TupleBirlikte ç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}");

Derleyici hediyeler:

Ö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 Item1ve Item2ayrış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.


1
Daha basit (ve bence tercih edilen) sözdizimini de kullanabileceğinizi unutmayınvar (sum, count) = DoStuff(Enumerable.Range(0, 10));
Abion47

@ Abion47 Her iki tip de farklıysa ne olur?
Yuval Itzchakov

Ayrıca puanlarınız "bu değişebilir bir yapı" ve " salt okunur alanları açığa çıkarır" nasıl anlaşır ?
CodesInChaos

@CodesInChaos Öyle değil. [Bunu] gördüm ( github.com/dotnet/corefx/blob/master/src/Common/src/System/… ), ancak derleyici tarafından yayımlanan şeyin bu olduğunu düşünmüyorum, çünkü yerel alanlar olamaz zaten salt okunur. Bence öneri yanlış yorumladığım "isterseniz bunları salt okunur yapabilirsiniz, ama bu size kalmış" anlamına geliyordu .
Yuval Itzchakov

1
Bazı nitler: "yığın üzerinde tahsis edilecekler" - sadece yerel değişkenler için geçerlidir. Şüphesiz bunu biliyorsunuz, ancak ne yazık ki, bunu ifade etme şekliniz, değer türlerinin her zaman yığın içinde yaşadığı efsanesini devam ettirecektir.
Peter Duniho

26

Arasındaki fark Tupleve ValueTupleolduğunu Tuplebaşvuru türü ve ValueTuplebir 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ı.


10

Her ikisinin de kaynağına baktım Tupleve ValueTuple. Fark şu ki Tuple, bir classve ValueTuplebir structuygulayan IEquatable.

Aracının Tuple == Tupledöner falseaynı örneği değildir, ancak eğer ValueTuple == ValueTupledöndürür trueaynı tür ve eğer Equalsgeri dönüş trueiçerdikleri değerlerin her biri için.


Yine de bundan daha fazlası.
BoltClock

2
@BoltClock Eğer detaylandırırsanız yorumunuz yapıcı olurdu
Peter Morris

3
Ayrıca, değer türlerinin yığına girmesi gerekmez. Aradaki fark, bu değişken depolandığında yığın olarak olabilecek ya da olmayabilecek bir referans yerine değeri anlamsal olarak temsil etmesidir.
16'da

6

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.Createfabrika yöntemleri ile oluşturulur . System.ValueTupleTipleri farklı System.Tupleki türleri:

  • onlar sınıflardan ziyade yapılardır,
  • salt okunur değil değişebilirler ve
  • üyeleri (Öğe1, Öğe2 vb.) özellikler yerine alanlardır.

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";

"Arity 0 ila 8". Ah, 0 demet içermelerini seviyorum. Bir tür boş tip olarak kullanılabilir ve class MyNonGenericType : MyGenericType<string, ValueTuple, int>vb. Gibi bazı tip parametrelerine ihtiyaç duyulmadığında jeneriklere izin verilir
Jeppe Stig Nielsen

6

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.


2
Yani teknik olarak bu bir benzerlik ve bir fark değil;)
JAD

2

Bu iki factoid hakkında hızlı bir açıklama eklemek için geç katılım:

  • onlar sınıflardan ziyade yapılardır
  • salt okunur değil değişebilirler

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.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.