Наименование в классах Tuple лучше, чем «Item1», «Item2»


204

Есть ли способ использовать класс Tuple, но указать имена элементов в нем?

Например:

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

Это возвращает идентификаторы для OrderGroupId, OrderTypeId, OrderSubTypeId и OrderRequirementId.

Было бы хорошо, чтобы пользователи моего метода знали, что есть что. (Когда вы вызываете метод, результаты являются result.Item1, result.Item2, result.Item3, result.Item4. Не ясно, какой из них какой.)

(Я знаю, что мог бы просто создать класс для хранения всех этих идентификаторов, но если эти идентификаторы уже имеют свои собственные классы, в которых они живут, и создание класса для возвращаемого значения этого одного метода кажется глупым.)


1
Тебе придется катиться самостоятельно - Tupleэто очень универсально, так что это все, что ты получаешь
BrokenGlass

НЕТ, вы не можете так делать, см. Эту ссылку для получения дополнительной информации. Msdn.microsoft.com/en-us/vcsharp/ee957397
Enigma State

1
Рискну сказать, что использование Tuple в качестве общедоступного типа данных для вашего API может быть не рекомендовано. Я обычно использую Tuple для недолговечных внутренних вещей, а не в качестве возвращаемого значения API.
Майк Бурдик

1
Голосуйте за эту функцию там: visualstudio.uservoice.com/forums/121579-visual-studio-2015/…
Джон

4
Он находится в рабочем списке C # 7. См. Github.com/dotnet/roslyn/issues/347
Филип Дин,

Ответы:


277

В C # 7.0 (Visual Studio 2017) есть новая конструкция, которая делает это:

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

68
Синтаксис есть List<(int first, int second)>. Мне пришлось загрузить пакет System.ValueTuple из NuGet, чтобы он работал в Visual Studio 2017.
Мэтт Дэвис,

14
Для создания стоимостиreturn (first: first, middle: middle, last: last);
Fiat

4
или только: return (first, middle, last);в .NET 4.7.1 (не уверен в 4.7.0)
watbywbarif

1
чтобы использовать его, вам нужно добавить пакет nuget System.ValueTuple
Alex G

11
Следует отметить, что C # 7 ValueTuple, как правило, большой, является изменяемым типом значения (struct), а Tupleявляется неизменным ссылочным типом (class). Насколько я знаю, нет способа получить ссылочный тип Tupleс понятными именами элементов.
dx_over_dt

51

До C # 7.0 не было никакого способа обойтись без определения собственного типа.


14
Я не могу поверить, что этот ответ принят со счетом 40. Вы могли бы по крайней мере показать, как класс с правильным конструктором может заменить это.
bytecode77

1
@ bytecode77 Что ж, довольно скоро этот ответ будет неправильным: github.com/dotnet/roslyn/issues/347
MarkPflug

Я видел эти предложения языковых фестивалей. Но до сих пор класс - единственное правильное решение для более сложных типов данных. Зачем вам принудительно использовать кортежи, несмотря ни на что (см. Другие ответы)
bytecode77

3
После выхода C # 7 это можно будет сделать: msdn.microsoft.com/en-us/magazine/mt595758.aspx
Бурак Каракуш

11
В Q 'есть c # 4 в качестве тега, поэтому, хотя этот ответ короткий, он все же правильный.
Стив Дрейк

33

Вот слишком сложная версия того, что вы спрашиваете:

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

}

Почему бы просто не сделать урок?


2
будет структура лучше в этом случае вместо класса?
deathrace

5
Небольшое преимущество, которое я вижу в этом, состоит в том, что он автоматически реализует оператор равенства, проверяя, что 2 экземпляра равны, если все элементы равны.
JSoet

8
Другим недостатком этого подхода является то, что Item1 и Item2 по-прежнему являются открытыми свойствами в MyTuple
RJFalconer

3
@deathrace Tuple сами по себе являются классами, поэтому, если вы хотите напрямую наследовать от Tuple<T, T2>вас, вы не можете быть структурой.
Чакрава

3
Я могу ошибаться, но я в основном использую кортеж везде, где я хочу вернуть объект, но не хочу определять конкретный класс ..
Jay

12

С .net 4 вы, возможно, могли бы взглянуть на ExpandoObject, однако, не использовать его в этом простом случае, поскольку ошибки во время компиляции становятся ошибками во время выполнения.

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.

Стоит упомянуть еще один анонимный тип внутри метода , но вам нужно создать класс, если вы хотите его вернуть.

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

10

Воспроизведение моего ответа из этого поста, так как он лучше подходит здесь.

Начиная с C # v7.0, теперь можно присваивать имена свойств кортежа, которые ранее использовались по умолчанию, предопределенным именам Item1, Item2таким как и так далее.

Называя свойства Tuple Literals :

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

Вывод на консоль:

Имя - RBT_Yoga, Возраст - 22, Страсть - Dosa

Возврат кортежа (с именованными свойствами) из метода :

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);
}

Вывод на консоль:

Данные сотрудника: Расик, Бихари, Расик-ПК, 1000

Создание списка кортежей с именованными свойствами

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

Вывод на консоль:

1 - корова 5 - куры 1 - самолет

Я надеюсь, что я охватил все. В случае, если есть что-то, что я пропустил, пожалуйста, дайте мне отзыв в комментариях.

Примечание . В моих фрагментах кода используется функция интерполяции строк C # v7, как подробно описано здесь .


3

MichaelMocko ответил отлично,

но я хочу добавить несколько вещей, которые я должен был выяснить,

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

выше линии даст вам ошибку времени компиляции, если вы используете .net framework <4.7

Так что если у вас есть проект, который использует .net Framework <4.7, и вы все еще хотите использовать ValueTuple, то workAround будет устанавливать этот пакет nuget



2

Если типы ваших предметов различны, вот класс, который я сделал, чтобы получить их более интуитивно.

Использование этого класса:

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

Исходный код:

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
Это похоже на большую работу за небольшую или нулевую отдачу. Оно имеет неинтуитивное ограничение (без дублирующих типов), и я нахожу идею извлечения значения только по одному его типу невероятно неинтуитивной и не могу придумать практического варианта его использования. Это эквивалентно созданию таблицы данных для сотрудников, затем принятию решения о поиске сотрудников по их имени (в отличие от уникального ключа) и последующему требованию, чтобы все сотрудники имели разные имена. Это не решение проблемы, а использование решения за счет создания дополнительной проблемы.
Флэттер

И пусть Бог помилует твою душу.
Джейми М.

1

Это очень раздражает, и я ожидаю, что будущие версии C # решат эту проблему. Я считаю, что проще всего использовать другой тип структуры данных или переименовать «элементы» для вашего здравого смысла и для здравомыслия других, читающих ваш код.

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

0

Я думаю, что я создал бы класс, но другой альтернативой являются выходные параметры.

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

Поскольку ваш кортеж содержит только целые числа, вы можете представить его с помощью Dictionary<string,int>

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

но я тоже не рекомендую.


0

Почему все делают жизнь такой сложной? Кортежи предназначены для достаточно временной обработки данных . Постоянная работа с Tuples усложнит понимание кода в какой-то момент. Создание классов для всего может в конечном итоге раздуть ваш проект.

Речь идет о балансе, однако ...

Кажется, ваша проблема - это то, для чего вы хотели бы получить урок. И просто для полноты, этот класс ниже также содержит конструкторы.


Это правильный шаблон для

  • Пользовательский тип данных
    • без дальнейшей функциональности. Методы получения и установки также могут быть расширены с помощью кода, получая / устанавливая закрытые члены с шаблоном имени «_orderGroupId», одновременно выполняя функциональный код.
  • Включая конструкторов. Вы также можете включить только один конструктор, если все свойства являются обязательными.
  • Если вы хотите использовать все конструкторы, всплывающее окно, как это, является правильным шаблоном, чтобы избежать дублирования кода.

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

Или, если вы хотите действительно просто: вы также можете использовать инициализаторы типов:

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

Я бы написал имена элементов в сводке ... поэтому, наведя указатель мыши на функцию helloworld (), текст скажет hello = Item1 и 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

Просто чтобы добавить в ответ @MichaelMocko. На данный момент у кортежей есть пара ошибок:

Вы не можете использовать их в деревьях выражений EF

Пример:

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();
}

Это не удастся скомпилировать с ошибкой «Дерево выражений может не содержать литерал кортежа». К сожалению, API деревьев выражений не был расширен за счет поддержки кортежей, когда они были добавлены в язык.

Отслеживание (и повышение) этой проблемы для обновлений: https://github.com/dotnet/roslyn/issues/12897

Чтобы обойти проблему, вы можете сначала привести ее к анонимному типу, а затем преобразовать значение в кортеж:

// 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();
}

Другой вариант - использовать 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();
}

Ссылки:

Вы не можете деконструировать их в лямбдах

Есть предложение добавить поддержку: https://github.com/dotnet/csharplang/issues/258

Пример:

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; })

Ссылки:

Они не будут сериализоваться

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); 
    }
}

Имена полей кортежей доступны только во время компиляции и полностью стираются во время выполнения.

Ссылки:


-1

Вы можете написать класс, который содержит кортеж.

Вам необходимо переопределить функции Equals и GetHashCode

и операторы == и! =.

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));
    }
}

вернется:

Правда

Правда


2
Если вы уже реализовали класс для этого типа данных, почему вы объявляете Tuple для базовых данных, а не просто для свойств?
bytecode77

Я хочу использовать атрибут tuple, который он сравнивает по значению в функции Equals
ss

Это может быть бонусом. Но с другой стороны, вы в основном создали класс со свойствами, которые варьируются от Item1 до ItemX. Я бы выбрал правильное именование и больше кода в Equals () вместо использования кортежа.
bytecode77

-1

Пример кортежа 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

                }     
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.