Краще іменування в класах Tuple, ніж "Item1", "Item2"


204

Чи існує спосіб використання класу Tuple, але вкажіть назви елементів у ньому?

Наприклад:

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

Це повертає ідентифікатори для OrderGroupId, OrderTypeId, OrderSubTypeId і OrderRequirementId.

Було б непогано дозволити користувачам мого методу знати, що це таке. (Коли ви викликаєте метод, результати - результат.Ітем1, результат.Ітем2, результат.Ітем3, результат.Ітем4. Не ясно, який з них є.)

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


1
Вам доведеться закатати свій власний - Tupleдуже загальний, тому це все, що ви отримаєте
BrokenGlass

НІ ви не можете цього зробити, дивіться це посилання для отримання додаткової інформації. Msdn.microsoft.com/en-us/vcsharp/ee957397
Enigma State

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

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

4
Це в робочому списку C # 7 Див. Github.com/dotnet/roslyn/isissue/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);
фіат

4
або лише: return (first, middle, last);у .NET 4.7.1 (не впевнений у 4.7.0)
watbywbarif

1
для того, щоб використовувати його, вам потрібно буде додати нульовий пакет System.ValueTuple
Alex G

11
Слід зазначити, що C # 7 ValueTuple, хоча зазвичай є великим, є типом змінного значення (структура), тоді як Tupleє незмінним еталонним типом (класом). Наскільки я знаю, немає способу отримати тип посилання Tupleз дружніми назвами предметів.
dx_over_dt

51

До C # 7.0, не було можливості цього зробити, якщо визначити власний тип.


13
Я не можу повірити, що ця відповідь прийнята з оцінкою 40. Ви могли б принаймні показати, як клас із належним конструктором може замінити це.
bytecode77

1
@ bytecode77 Ну, досить скоро ця відповідь стане неправильною: github.com/dotnet/roslyn/isissue/347
MarkPflug

Я бачив ці пропозиції щодо мовної кастрації. Але до цих пір клас є єдиним правильним рішенням для більш складних типів даних. Чому б ви хотіли насильно використовувати Tuples незалежно від чого (див. Інші відповіді)
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
Буде структура краще в цьому випадку замість класу?
смертна раса

5
Незначна перевага, яку я бачу в цьому, полягає в тому, що він автоматично реалізує оператор рівних, перевіряючи, що два екземпляри рівні, якщо всі елементи рівні.
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);
}

Вихід на консолі:

Деталі працівника: Rasik, Bihari, Rasik-PC, 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, встановив би цей пакунок нута



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
Це здається великою роботою за мало-ніяких виплат. Він має неінтуїтивне обмеження (відсутність дублюючих типів), і я вважаю, що витягнути значення лише лише за його типом неймовірно неінтуїтивно, і я не можу придумати його практичного використання. Це еквівалент складання таблиці даних для співробітників, а потім прийняття рішення про отримання працівників за їхнім прізвищем (на відміну від унікального ключа) і згодом вимагати від усіх співробітників різних імен. Це не є вирішенням проблеми, це використання рішення ціною створення додаткової проблеми.
Flater

І нехай Бог помилує вашу душу.
Джеймі М.

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 (), текст скаже привіт = 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/isissue/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/isissue/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
Якщо ви вже реалізували клас для цього типу даних, чому ви оголошуєте кортеж для базових даних замість просто властивостей?
bytecode77

Я хочу використовувати атрибут tuple, який він по значенню виконує у функції «Рівно»
ss

Це може бути бонусом. Але з іншого боку, ви в основному створили клас із властивостями, що варіюються від Item1 до ItemX. Я б вибрав правильне іменування та більше коду в рівності () над використанням кортежу.
bytecode77

-1

Приклад кортежу № 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.