Melhor nomeação nas classes de tupla do que "Item1", "Item2"

204

Existe uma maneira de usar uma classe Tuple, mas forneça os nomes dos itens nela?

Por exemplo:

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

Isso retorna os IDs para OrderGroupId, OrderTypeId, OrderSubTypeId e OrderRequirementId.

Seria bom informar aos usuários do meu método qual é qual. (Quando você chama o método, os resultados são result.Item1, result.Item2, result.Item3, result.Item4. Não está claro qual é qual.)

(Eu sei que eu poderia criar uma classe para armazenar todos esses IDs, mas esses IDs já têm suas próprias classes em que vivem e criar uma classe para o valor de retorno desse método parece bobo.)

Vaccano
fonte
1
Você vai ter que rolar o seu próprio - Tupleé muito genérico, então isso é tudo que você começa
BrokenGlass
Não, você não pode fazer assim, veja este link para mais informações msdn.microsoft.com/en-us/vcsharp/ee957397
Estado Enigma
1
Atrevo-me a dizer que o uso de uma Tupla como um tipo de dados voltado para o público da sua API pode não ser recomendado. Normalmente, uso a Tupla para coisas internas de curta duração, não como valor de retorno de uma API.
Mike Burdick
4
Está na lista de trabalho do C # 7. Veja github.com/dotnet/roslyn/issues/347
Philip Ding

Respostas:

277

No C # 7.0 (Visual Studio 2017), há uma nova construção para fazer isso:

(string first, string middle, string last) LookupName(long id)
MichaelMocko
fonte
68
A sintaxe é List<(int first, int second)>. Eu tive que baixar o pacote System.ValueTuple de NuGet para obtê-lo para trabalhar no Visual Studio 2017.
Matt Davis
14
Para criar o valorreturn (first: first, middle: middle, last: last);
fiat
4
ou apenas: return (first, middle, last);no .NET 4.7.1 (não tenho certeza para o 4.7.0) #
watbywbarif
1
para usá-lo, você precisará adicionar o pacote System.ValueTuple nuget
Alex G
11
Deve-se notar que os C # 7 ValueTuple, embora geralmente ótimos, são um tipo de valor mutável (struct), enquanto Tupleé um tipo de referência imutável (classe). Tanto quanto eu sei, não há como obter um tipo de referência Tuplecom nomes de itens amigáveis.
Dx_over_dt 11/07/19
51

Até o C # 7.0, não havia como fazer isso sem definir seu próprio tipo.

MarkPflug
fonte
13
Não acredito que essa resposta seja aceita com uma pontuação de 40. Você poderia pelo menos ter mostrado como uma classe com um construtor adequado pode substituí-la.
precisa
1
@ bytecode77 Bem, muito em breve essa resposta vai ser para cima errado: github.com/dotnet/roslyn/issues/347
MarkPflug
Eu já vi essas propostas de festivais de idiomas. Mas até agora, uma classe é a única solução adequada para tipos de dados mais complexos. Por que você iria querer usar força Tuples não importa o que (ver outras respostas)
bytecode77
3
Após o lançamento do C # 7, será possível fazer isso: msdn.microsoft.com/en-us/magazine/mt595758.aspx
Burak Karakuş
11
O Q 'tem c # 4 como tag, portanto, embora essa resposta seja curta, ela ainda está correta.
Steve Drake
33

Aqui está uma versão muito complicada do que você está perguntando:

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

}

Por que não apenas fazer uma aula?

escocês
fonte
2
estrutura seria melhor neste caso em vez de classe?
deathrace
5
A pequena vantagem que vejo disso é que ele implementa automaticamente o operador igual, verificando se 2 instâncias são iguais se os itens forem todos iguais.
JSoet
8
Outra desvantagem dessa abordagem é que Item1 e Item2 ainda são propriedades públicas sobre MyTuple
RJFalconer
3
As tuplas @deathrace são classes, portanto, se você deseja herdar diretamente, Tuple<T, T2>não pode ser uma estrutura.
Chakrava
3
Posso estar errado, mas geralmente uso tupla sempre que quero retornar um objeto, mas não quero definir uma classe específica. #
214 Jay Jay
12

Com o .net 4, talvez você possa olhar para ExpandoObject, no entanto, não o usar neste caso simples, pois o que seriam erros em tempo de compilação se tornam erros em tempo de execução.

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.

Outra coisa que vale a pena mencionar é um tipo anônimo dentro de um método , mas você precisa criar uma classe se quiser devolvê-la.

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

Reproduzindo minha resposta deste post, pois é um ajuste melhor aqui.

Começando C # v7.0, agora é possível nomear as propriedades tupla que anteriormente usados para padrão para nomes predefinidos como Item1, Item2e assim por diante.

Nomeando as propriedades de literais de tupla :

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

A saída no console:

Nome - RBT_Yoga, Idade - 22, Paixão - Dosa

Retornando tupla (tendo nomeado propriedades) de um método :

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

A saída no console:

Detalhes do funcionário: Rasik, Bihari, Rasik-PC, 1000

Criando uma lista de Tuplas com propriedades nomeadas

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

Saída no console:

1 - vaca 5 - galinhas 1 - avião

Espero ter coberto tudo. No caso, há algo que eu perdi, por favor, me dê um feedback nos comentários.

Nota : Meus trechos de código estão usando o recurso de interpolação de string do C # v7, conforme detalhado aqui .

RBT
fonte
3

MichaelMocko Respondido é ótimo,

mas quero acrescentar algumas coisas que tive que descobrir

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

A linha acima fornecerá um erro de tempo de compilação se você estiver usando o .net framework <4.7

Portanto, se você tiver um projeto que esteja usando a estrutura .net <4.7 e ainda desejar usar o ValueTuple do que o workAround, estaria instalando este pacote de nuget

Mihir Dave
fonte
2

Não, você não pode nomear os membros da tupla.

O meio seria usar ExpandoObject em vez de Tuple.

Joe Mancuso
fonte
2

Se os tipos de itens são todos diferentes, aqui está uma aula que fiz para obtê-los de forma mais intuitiva.

O uso desta classe:

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

Código fonte:

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; } }
}
hebinda
fonte
4
Parece muito trabalho com pouco ou nenhum retorno. Ele tem uma limitação não intuitiva (sem tipos duplicados), e acho a idéia de recuperar um valor apenas por seu tipo incrivelmente intuitiva e não consigo pensar em um caso de uso prático para ele. Isso equivale a criar uma tabela de dados para os funcionários e decidir recuperar os funcionários pelo primeiro nome (em oposição a uma chave exclusiva) e, posteriormente, exigir que todos os funcionários tenham nomes diferentes. Esta não é uma solução para um problema, está usando uma solução ao custo de criar um problema extra.
Flater
E que Deus tenha piedade de sua alma.
21719 Jamie M.
1

Isso é muito chato e espero que versões futuras do C # atendam a essa necessidade. Acho que a solução mais fácil é usar um tipo diferente de estrutura de dados ou renomear os "itens" para sua sanidade e para a sanidade de outras pessoas que leem seu código.

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

Eu acho que criaria uma classe, mas outra alternativa são os parâmetros de saída.

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

Como sua tupla contém apenas números inteiros, você pode representá-la com um Dictionary<string,int>

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

mas também não recomendo.

Jonas Elfström
fonte
0

Por que todo mundo está tornando a vida tão difícil? As tuplas são para processamento de dados temporário . Trabalhar com tuplas o tempo todo tornará o código muito difícil de entender em algum momento. Criando classes para tudo pode eventualmente inchar seu projeto.

É sobre equilíbrio, no entanto ...

Seu problema parece ser algo para o qual você deseja uma aula. E apenas por uma questão de integridade, esta classe abaixo também contém construtores.


Esse é o padrão adequado para

  • Um tipo de dados personalizado
    • sem mais funcionalidade. Os getters e setters também podem ser expandidos com o código, obtendo / configurando membros privados com o padrão de nome "_orderGroupId", enquanto também executa o código funcional.
  • Incluindo construtores. Você também pode optar por incluir apenas um construtor se todas as propriedades forem obrigatórias.
  • Se você deseja usar todos os construtores, borbulhar como este é o padrão adequado para evitar código duplicado.

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

Ou, se você quiser realmente simples: Você também pode usar inicializadores de tipo:

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;
}
bytecode77
fonte
0

Eu escreveria os nomes dos itens no summay .. então, passando o mouse sobre a função helloworld (), o texto dirá hello = Item1 e 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);
}
Kim Yang Jacobsen
fonte
0

Só para adicionar à resposta @MichaelMocko. As tuplas têm algumas dicas no momento:

Você não pode usá-los em árvores de expressão EF

Exemplo:

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

Não será possível compilar com o erro "Uma árvore de expressão pode não conter uma tupla literal". Infelizmente, a API das árvores de expressão não foi expandida com suporte para tuplas quando elas foram adicionadas ao idioma.

Acompanhe (e vote novamente) este problema para as atualizações: https://github.com/dotnet/roslyn/issues/12897

Para contornar o problema, você pode convertê-lo em tipo anônimo primeiro e depois converter o valor em tupla:

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

Outra opção é usar 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();
}

Referências:

Você não pode desconstruí-los em lambdas

Há uma proposta para adicionar o suporte: https://github.com/dotnet/csharplang/issues/258

Exemplo:

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

Referências:

Eles não serializam bem

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

Os nomes dos campos de tupla estão disponíveis apenas em tempo de compilação e são completamente eliminados em tempo de execução.

Referências:

Eugene Kulabuhov
fonte
-1

Você pode escrever uma classe que contém a tupla.

Você precisa substituir as funções Equals e GetHashCode

e os operadores == e! =.

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

retornará:

Verdade

Verdade

ss
fonte
2
Se você já implementou uma classe para esse tipo de dados, por que você declara uma Tupla para os dados subjacentes em vez de apenas propriedades?
precisa
Eu quero usar o atributo de tupla que compara por valor na função Igual
ss
Isso pode ser um bônus. Mas, por outro lado, você basicamente criou uma classe com propriedades que variam do Item1 ao ItemX. Eu escolheria a nomeação adequada e mais código em Equals () ao invés de usar uma tupla.
precisa
-1

Exemplo de tupla 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

                }     
Sumesh Es
fonte