Lepsze nazewnictwo w klasach Tuple niż „Item1”, „Item2”


204

Czy istnieje sposób na użycie klasy Tuple, ale podać w niej nazwy przedmiotów?

Na przykład:

public Tuple<int, int, int int> GetOrderRelatedIds()

Zwraca identyfikatory OrderGroupId, OrderTypeId, OrderSubTypeId i OrderRequirementId.

Byłoby miło poinformować użytkowników mojej metody, która jest która. (Po wywołaniu metody wyniki są następujące: item1, result.Item2, result.Item3, result.Item4. Nie jest jasne, który jest który.)

(Wiem, że mógłbym po prostu stworzyć klasę do przechowywania wszystkich tych identyfikatorów, ale jeśli identyfikatory te mają już własne klasy, w których żyją, a tworzenie klasy dla wartości zwracanej dla tej metody wydaje się głupie.)


1
Musisz rzucić własnym - Tuplejest bardzo ogólny, więc to wszystko, co dostajesz
BrokenGlass

NIE, nie możesz tego zrobić, zobacz ten link, aby uzyskać więcej informacji. Msdn.microsoft.com/en-us/vcsharp/ee957397
Enigma State

1
Zaryzykuję stwierdzenie, że użycie Tuple jako publicznego typu danych dla interfejsu API może nie być zalecane. Zwykle używam Tuple do krótkotrwałych rzeczy wewnętrznych, a nie jako wartości zwracanej API.
Mike Burdick,


4
Jest na roboczej liście C # 7 Patrz github.com/dotnet/roslyn/issues/347
Philip Ding

Odpowiedzi:


277

W C # 7.0 (Visual Studio 2017) wprowadzono nową konstrukcję:

(string first, string middle, string last) LookupName(long id)

68
Składnia jest następująca List<(int first, int second)>. Musiałem pobrać pakiet System.ValueTuple z NuGet, aby uruchomić go w Visual Studio 2017.
Matt Davis

14
Aby utworzyć wartośćreturn (first: first, middle: middle, last: last);
fiat

4
lub tylko: return (first, middle, last);w .NET 4.7.1 (
niepewny

1
aby go użyć, musisz dodać pakiet nuget System.ValueTuple
Alex G

11
Należy zauważyć, że C # 7 ValueTuple, chociaż zwykle świetne, jest zmiennym typem wartości (struct), podczas gdy Tuplejest niezmiennym typem odniesienia (klasa). O ile mi wiadomo, nie ma sposobu na uzyskanie typu referencyjnego Tuplez przyjaznymi nazwami przedmiotów.
dx_over_dt

51

Aż do wersji C # 7.0 nie było możliwości zrobienia tego bez zdefiniowania własnego typu.


13
Nie mogę uwierzyć, że ta odpowiedź jest akceptowana z wynikiem 40. Mogłeś przynajmniej pokazać, jak klasa z właściwym konstruktorem może to zastąpić.
bytecode77

1
@ bytecode77 Cóż, wkrótce ta odpowiedź będzie zupełnie
MarkPflug 19.08.16

Widziałem te propozycje festiwali językowych. Ale do tej pory klasa jest jedynym właściwym rozwiązaniem dla bardziej złożonych typów danych. Dlaczego chcesz siłą używać Tuple bez względu na wszystko (zobacz inne odpowiedzi)
bytecode77

3
Po wydaniu C # 7 będzie można to zrobić: msdn.microsoft.com/en-us/magazine/mt595758.aspx
Burak Karakuş

11
Q 'ma c # 4 jako znacznik, więc chociaż ta odpowiedź jest krótka, nadal jest poprawna.
Steve Drake

33

Oto zbyt skomplikowana wersja tego, o co pytasz:

class MyTuple : Tuple<int, int>
{
    public MyTuple(int one, int two)
        :base(one, two)
    {

    }

    public int OrderGroupId { get{ return this.Item1; } }
    public int OrderTypeId { get{ return this.Item2; } }

}

Dlaczego nie zrobić klasy?


2
czy struct byłby lepszy w tym przypadku zamiast klasy?
deathrace

5
Niewielka zaleta, którą widzę, polega na tym, że automatycznie implementuje operator równości, sprawdzając, czy 2 wystąpienia są równe, jeśli wszystkie elementy są równe.
JSoet

8
Inną wadą tego podejścia jest to, że Item1 i Item2 są nadal publicznymi właściwościami MyTuple
RJFalconer

3
@ Deathrace Tuple same są klasami, więc jeśli chcesz bezpośrednio dziedziczyć po Tuple<T, T2>sobie, nie możesz być strukturą.
Chakrava

3
Mogę się mylić, ale najczęściej używam krotki wszędzie tam, gdzie chcę zwrócić obiekt, ale nie chcę definiować konkretnej klasy ..
Jay

12

Z .net 4 możesz być może spojrzeć na ExpandoObject, jednak nie używaj go w tym prostym przypadku, ponieważ to, co byłoby błędami kompilacji, stało się błędem czasu wykonywania.

class Program
{
    static void Main(string[] args)
    {
        dynamic employee, manager;

        employee = new ExpandoObject();
        employee.Name = "John Smith";
        employee.Age = 33;

        manager = new ExpandoObject();
        manager.Name = "Allison Brown";
        manager.Age = 42;
        manager.TeamSize = 10;

        WritePerson(manager);
        WritePerson(employee);
    }
    private static void WritePerson(dynamic person)
    {
        Console.WriteLine("{0} is {1} years old.",
                          person.Name, person.Age);
        // The following statement causes an exception
        // if you pass the employee object.
        // Console.WriteLine("Manages {0} people", person.TeamSize);
    }
}
// This code example produces the following output:
// John Smith is 33 years old.
// Allison Brown is 42 years old.

Coś innego, o czym warto wspomnieć, to anonimowy typ metody , ale musisz utworzyć klasę, jeśli chcesz ją zwrócić.

var MyStuff = new
    {
        PropertyName1 = 10,
        PropertyName2 = "string data",
        PropertyName3 = new ComplexType()
    };

10

Odtworzenie mojej odpowiedzi z tego postu, ponieważ lepiej tutaj pasuje.

Począwszy C # 7.0, teraz jest to możliwe, aby wymienić właściwości krotki, które wcześniej używane do ustawień predefiniowanych nazwach takich jak Item1, Item2i tak dalej.

Nazewnictwo właściwości literałów Tuple :

var myDetails = (MyName: "RBT_Yoga", MyAge: 22, MyFavoriteFood: "Dosa");
Console.WriteLine($"Name - {myDetails.MyName}, Age - {myDetails.MyAge}, Passion - {myDetails.MyFavoriteFood}");

Dane wyjściowe na konsoli:

Imię - RBT_Yoga, Wiek - 22 lat, Pasja - Dosa

Zwracanie krotki (o nazwanych właściwościach) z metody :

static void Main(string[] args)
{
    var empInfo = GetEmpInfo();
    Console.WriteLine($"Employee Details: {empInfo.firstName}, {empInfo.lastName}, {empInfo.computerName}, {empInfo.Salary}");
}

static (string firstName, string lastName, string computerName, int Salary) GetEmpInfo()
{
    //This is hardcoded just for the demonstration. Ideally this data might be coming from some DB or web service call
    return ("Rasik", "Bihari", "Rasik-PC", 1000);
}

Dane wyjściowe na konsoli:

Dane pracownika: Rasik, Bihari, Rasik-PC, 1000

Tworzenie listy krotek o nazwanych właściwościach

var tupleList = new List<(int Index, string Name)>
{
    (1, "cow"),
    (5, "chickens"),
    (1, "airplane")
};

foreach (var tuple in tupleList)
    Console.WriteLine($"{tuple.Index} - {tuple.Name}");

Dane wyjściowe na konsoli:

1 - krowa 5 - kurczaki 1 - samolot

Mam nadzieję, że wszystko omówiłem. Jeśli coś mi umknęło, proszę o komentarz w komentarzach.

Uwaga : Moje fragmenty kodu używają funkcji interpolacji ciągów w C # v7, jak wyszczególniono tutaj .


3

MichaelMocko Ansided jest świetny,

ale chcę dodać kilka rzeczy, które musiałem wymyślić

(string first, string middle, string last) LookupName(long id)

powyżej linii da ci błąd czasu kompilacji, jeśli używasz .NET Framework <4.7

Więc jeśli masz projekt, który używa frameworka .net <4.7 i nadal chcesz używać ValueTuple niż workAround, instalowałby ten pakiet nuget



2

Jeśli rodzaje twoich przedmiotów są różne, oto klasa, którą stworzyłem, aby uzyskać je bardziej intuicyjnie.

Zastosowanie tej klasy:

var t = TypedTuple.Create("hello", 1, new MyClass());
var s = t.Get<string>();
var i = t.Get<int>();
var c = t.Get<MyClass>();

Kod źródłowy:

public static class TypedTuple
{
    public static TypedTuple<T1> Create<T1>(T1 t1)
    {
        return new TypedTuple<T1>(t1);
    }

    public static TypedTuple<T1, T2> Create<T1, T2>(T1 t1, T2 t2)
    {
        return new TypedTuple<T1, T2>(t1, t2);
    }

    public static TypedTuple<T1, T2, T3> Create<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
    {
        return new TypedTuple<T1, T2, T3>(t1, t2, t3);
    }

    public static TypedTuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4)
    {
        return new TypedTuple<T1, T2, T3, T4>(t1, t2, t3, t4);
    }

    public static TypedTuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
    {
        return new TypedTuple<T1, T2, T3, T4, T5>(t1, t2, t3, t4, t5);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6>(t1, t2, t3, t4, t5, t6);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7>(t1, t2, t3, t4, t5, t6, t7);
    }

    public static TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
    {
        return new TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8>(t1, t2, t3, t4, t5, t6, t7, t8);
    }

}

public class TypedTuple<T>
{
    protected Dictionary<Type, object> items = new Dictionary<Type, object>();

    public TypedTuple(T item1)
    {
        Item1 = item1;
    }

    public TSource Get<TSource>()
    {
        object value;
        if (this.items.TryGetValue(typeof(TSource), out value))
        {
            return (TSource)value;
        }
        else
            return default(TSource);
    }

    private T item1;
    public T Item1 { get { return this.item1; } set { this.item1 = value; this.items[typeof(T)] = value; } }
}

public class TypedTuple<T1, T2> : TypedTuple<T1>
{
    public TypedTuple(T1 item1, T2 item2)
        : base(item1)
    {
        Item2 = item2;
    }

    private T2 item2;
    public T2 Item2 { get { return this.item2; } set { this.item2 = value; this.items[typeof(T2)] = value; } }
}

public class TypedTuple<T1, T2, T3> : TypedTuple<T1, T2>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3)
        : base(item1, item2)
    {
        Item3 = item3;
    }

    private T3 item3;
    public T3 Item3 { get { return this.item3; } set { this.item3 = value; this.items[typeof(T3)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4> : TypedTuple<T1, T2, T3>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4)
        : base(item1, item2, item3)
    {
        Item4 = item4;
    }

    private T4 item4;
    public T4 Item4 { get { return this.item4; } set { this.item4 = value; this.items[typeof(T4)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5> : TypedTuple<T1, T2, T3, T4>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
        : base(item1, item2, item3, item4)
    {
        Item5 = item5;
    }

    private T5 item5;
    public T5 Item5 { get { return this.item5; } set { this.item5 = value; this.items[typeof(T5)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6> : TypedTuple<T1, T2, T3, T4, T5>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
        : base(item1, item2, item3, item4, item5)
    {
        Item6 = item6;
    }

    private T6 item6;
    public T6 Item6 { get { return this.item6; } set { this.item6 = value; this.items[typeof(T6)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7> : TypedTuple<T1, T2, T3, T4, T5, T6>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
        : base(item1, item2, item3, item4, item5, item6)
    {
        Item7 = item7;
    }

    private T7 item7;
    public T7 Item7 { get { return this.item7; } set { this.item7 = value; this.items[typeof(T7)] = value; } }
}

public class TypedTuple<T1, T2, T3, T4, T5, T6, T7, T8> : TypedTuple<T1, T2, T3, T4, T5, T6, T7>
{
    public TypedTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
        : base(item1, item2, item3, item4, item5, item6, item7)
    {
        Item8 = item8;
    }

    private T8 item8;
    public T8 Item8 { get { return this.item8; } set { this.item8 = value; this.items[typeof(T8)] = value; } }
}

4
To wydaje się dużo pracy za niewielką lub żadną wypłatę. Ma nieintuicyjne ograniczenie (bez duplikatów), a myślę, że odzyskanie wartości tylko przez sam typ jest niezwykle nieintuicyjne i nie mogę wymyślić praktycznego zastosowania. Jest to odpowiednik utworzenia tabeli danych dla pracowników, a następnie podjęcia decyzji o odzyskaniu pracowników według ich imienia (w przeciwieństwie do unikalnego klucza), a następnie wymaganie od wszystkich pracowników posiadania różnych imion. To nie jest rozwiązanie problemu, używa rozwiązania kosztem stworzenia dodatkowego problemu.
Flater

I niech Bóg zlituje się nad twoją duszą.
Jamie M.

1

Jest to bardzo denerwujące i spodziewam się, że przyszłe wersje C # zaspokoją tę potrzebę. Uważam, że najłatwiejszym rozwiązaniem jest albo użycie innego typu struktury danych, albo zmiana nazwy „elementów” dla twojego zdrowia psychicznego i dla zdrowia innych osób czytających Twój kod.

Tuple<ApiResource, JSendResponseStatus> result = await SendApiRequest();
ApiResource apiResource = result.Item1;
JSendResponseStatus jSendStatus = result.Item2;

0

Myślę, że stworzyłbym klasę, ale inną alternatywą są parametry wyjściowe.

public void GetOrderRelatedIds(out int OrderGroupId, out int OrderTypeId, out int OrderSubTypeId, out int OrderRequirementId)

Ponieważ twoja krotka zawiera tylko liczby całkowite, możesz ją przedstawić za pomocą Dictionary<string,int>

var orderIds = new Dictionary<string, int> {
    {"OrderGroupId", 1},
    {"OrderTypeId", 2},
    {"OrderSubTypeId", 3},
    {"OrderRequirementId", 4}.
};

ale ja też tego nie polecam.


0

Dlaczego wszyscy utrudniają życie. Krotki służą raczej do tymczasowego przetwarzania danych . Ciągła praca z Tuplesem w pewnym momencie bardzo utrudni zrozumienie kodu. Tworzenie klas dla wszystkiego może ostatecznie nadmuchać twój projekt.

Chodzi jednak o równowagę ...

Twój problem wydaje się być czymś, do czego chcesz klasę. I tylko ze względu na kompletność, ta klasa poniżej zawiera również konstruktory.


To jest właściwy wzorzec dla

  • Niestandardowy typ danych
    • bez dalszej funkcjonalności. Pozyskiwania i ustawiania można również rozszerzać za pomocą kodu, uzyskując / ustawiając prywatnych członków o wzorcu nazwy „_orderGroupId”, jednocześnie wykonując kod funkcjonalny.
  • W tym konstruktory. Możesz także dołączyć tylko jednego konstruktora, jeśli wszystkie właściwości są obowiązkowe.
  • Jeśli chcesz używać wszystkich konstruktorów, takie propagowanie jest właściwym wzorcem, aby uniknąć powielania kodu.

public class OrderRelatedIds
{
    public int OrderGroupId { get; set; }
    public int OrderTypeId { get; set; }
    public int OrderSubTypeId { get; set; }
    public int OrderRequirementId { get; set; }

    public OrderRelatedIds()
    {
    }
    public OrderRelatedIds(int orderGroupId)
        : this()
    {
        OrderGroupId = orderGroupId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId)
        : this(orderGroupId)
    {
        OrderTypeId = orderTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId)
        : this(orderGroupId, orderTypeId)
    {
        OrderSubTypeId = orderSubTypeId;
    }
    public OrderRelatedIds(int orderGroupId, int orderTypeId, int orderSubTypeId, int orderRequirementId)
        : this(orderGroupId, orderTypeId, orderSubTypeId)
    {
        OrderRequirementId = orderRequirementId;
    }
}

Lub, jeśli chcesz to naprawdę proste: Możesz również użyć inicjatorów typu:

OrderRelatedIds orders = new OrderRelatedIds
{
    OrderGroupId = 1,
    OrderTypeId = 2,
    OrderSubTypeId = 3,
    OrderRequirementId = 4
};

public class OrderRelatedIds
{
    public int OrderGroupId;
    public int OrderTypeId;
    public int OrderSubTypeId;
    public int OrderRequirementId;
}

0

Chciałbym napisać nazwy przedmiotów w podsumowaniu .. więc po najechaniu myszką na funkcję helloworld () tekst powie: hello = Item1 i world = Item2

 helloworld("Hi1,Hi2");

/// <summary>
/// Return hello = Item1 and world Item2
/// </summary>
/// <param name="input">string to split</param>
/// <returns></returns>
private static Tuple<bool, bool> helloworld(string input)
{
    bool hello = false;
    bool world = false;
    foreach (var hw in input.Split(','))
    {
        switch (hw)
        {
            case "Hi1":
                hello= true;
                break;
            case "Hi2":
                world= true;
                break;
        }

    }
    return new Tuple<bool, bool>(hello, world);
}

0

Wystarczy dodać do odpowiedzi @MichaelMocko. W tej chwili Tuple ma kilka gotch:

Nie można ich używać w drzewach wyrażeń EF

Przykład:

public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        // Selecting as Tuple
        .Select(person => (person.Name, person.Surname))
        .First();
}

Nie uda się skompilować z błędem „Drzewo wyrażeń może nie zawierać literału krotkowego”. Niestety interfejs API drzew wyrażeń nie został rozszerzony o obsługę krotek, gdy zostały one dodane do języka.

Śledź ten problem i aktualizuj go: https://github.com/dotnet/roslyn/issues/12897

Aby obejść ten problem, możesz najpierw rzucić go na typ anonimowy, a następnie przekonwertować wartość na krotkę:

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => new { person.Name, person.Surname })
        .ToList()
        .Select(person => (person.Name, person.Surname))
        .First();
}

Inną opcją jest użycie ValueTuple.Create:

// Will work
public static (string name, string surname) GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname))
        .First();
}

Bibliografia:

Nie można ich zdekonstruować w lambdach

Istnieje propozycja dodania obsługi: https://github.com/dotnet/csharplang/issues/258

Przykład:

public static IQueryable<(string name, string surname)> GetPersonName(this PersonContext ctx, int id)
{
    return ctx.Persons
        .Where(person => person.Id == id)
        .Select(person => ValueTuple.Create(person.Name, person.Surname));
}

// This won't work
ctx.GetPersonName(id).Select((name, surname) => { return name + surname; })

// But this will
ctx.GetPersonName(id).Select(t => { return t.name + t.surname; })

Bibliografia:

Nie będą ładnie serializować

using System;
using Newtonsoft.Json;

public class Program
{
    public static void Main() {
        var me = (age: 21, favoriteFood: "Custard");
        string json = JsonConvert.SerializeObject(me);

        // Will output {"Item1":21,"Item2":"Custard"}
        Console.WriteLine(json); 
    }
}

Nazwy pól krotek są dostępne tylko w czasie kompilacji i są całkowicie usuwane w czasie wykonywania.

Bibliografia:


-1

Możesz napisać klasę zawierającą krotkę.

Musisz zastąpić funkcje Equals i GetHashCode

oraz operatory == i! =.

class Program
{
    public class MyTuple
    {
        private Tuple<int, int> t;

        public MyTuple(int a, int b)
        {
            t = new Tuple<int, int>(a, b);
        }

        public int A
        {
            get
            {
                return t.Item1;
            }
        }

        public int B
        {
            get
            {
                return t.Item2;
            }
        }

        public override bool Equals(object obj)
        {
            return t.Equals(((MyTuple)obj).t);
        }

        public override int GetHashCode()
        {
            return t.GetHashCode();
        }

        public static bool operator ==(MyTuple m1, MyTuple m2)
        {
            return m1.Equals(m2);
        }

        public static bool operator !=(MyTuple m1, MyTuple m2)
        {
            return !m1.Equals(m2);
        }
    }

    static void Main(string[] args)
    {
        var v1 = new MyTuple(1, 2);
        var v2 = new MyTuple(1, 2);

        Console.WriteLine(v1 == v2);

        Dictionary<MyTuple, int> d = new Dictionary<MyTuple, int>();
        d.Add(v1, 1);

        Console.WriteLine(d.ContainsKey(v2));
    }
}

wróci:

Prawdziwe

Prawdziwe


2
Jeśli już zaimplementowałeś klasę dla tego typu danych, dlaczego deklarujesz krotkę dla danych bazowych zamiast tylko właściwości?
bytecode77

Chcę użyć atrybutu krotki, który kompresuje według wartości w funkcji równości
ss

To może być bonus. Ale z drugiej strony w zasadzie stworzyłeś klasę o właściwościach od Item1 do ItemX. Wolałbym wybrać właściwe nazewnictwo i więcej kodu w Equals () niż za pomocą krotki.
bytecode77

-1

Przykład krotki C # 7

var tuple = TupleExample(key, value);

     private (string key1, long value1) ValidateAPIKeyOwnerId(string key, string value)
            {
                return (key, value);
            }
      if (!string.IsNullOrEmpty(tuple.key1) && tuple.value1 > 0)
          {
                    //your code

                }     
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.