Mejor denominación en las clases de tupla que "Item1", "Item2"

204

¿Hay alguna manera de usar una clase Tuple, pero proporcione los nombres de los elementos que contiene?

Por ejemplo:

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

Eso devuelve los identificadores para OrderGroupId, OrderTypeId, OrderSubTypeId y OrderRequirementId.

Sería bueno que los usuarios de mi método sepan cuál es cuál. (Cuando llama al método, los resultados son result.Item1, result.Item2, result.Item3, result.Item4. No está claro cuál es cuál).

(Sé que podría crear una clase para contener todos estos ID, pero estos ID ya tienen sus propias clases en las que viven y hacer una clase para el valor de retorno de este método parece una tontería).

Vaccano
fuente
1
Tendrás que rodar el tuyo, Tuplees muy genérico, así que eso es todo lo que obtienes
BrokenGlass
NO, no puede hacer así, vea este enlace para obtener más información msdn.microsoft.com/en-us/vcsharp/ee957397
Enigma State
1
Me atrevería a decir que no se recomienda usar una Tuple como tipo de datos público para su API. Normalmente uso Tuple para cosas internas de corta duración, no como un valor de retorno de una API.
Mike Burdick
44
Está en la lista de trabajo de C # 7. Consulte github.com/dotnet/roslyn/issues/347
Philip Ding el

Respuestas:

277

En C # 7.0 (Visual Studio 2017) hay una nueva construcción para hacer eso:

(string first, string middle, string last) LookupName(long id)
MichaelMocko
fuente
68
La sintaxis es la siguiente List<(int first, int second)>. Tuve que descargar el paquete System.ValueTuple de NuGet para que funcione en Visual Studio 2017.
Matt Davis,
14
Para crear el valorreturn (first: first, middle: middle, last: last);
fiat
44
o solo: return (first, middle, last);en .NET 4.7.1 (no estoy seguro para 4.7.0)
watbywbarif
1
para usarlo, necesitará agregar el paquete Nuget System.ValueTuple
Alex G
11
Cabe señalar que los C # 7 ValueTuple, aunque generalmente son geniales, son un tipo de valor mutable (estructura), mientras que Tuplees un tipo de referencia inmutable (clase). Hasta donde sé, no hay forma de obtener un tipo de referencia Tuplecon nombres de elementos amigables.
dx_over_dt
51

Hasta C # 7.0, no había manera de hacer esto menos que definir su propio tipo.

MarkPflug
fuente
14
No puedo creer que esta respuesta sea aceptada con una puntuación de 40. Al menos podría haber mostrado cómo una clase con un constructor adecuado puede sustituir esto.
bytecode77
1
@ bytecode77 Bueno, muy pronto esta respuesta será incorrecta: github.com/dotnet/roslyn/issues/347
MarkPflug
He visto estas propuestas de fiestas lingüísticas. Pero hasta ahora, una clase es la única solución adecuada para tipos de datos más complejos. ¿Por qué querrías usar Tuples por la fuerza sin importar qué? (Ver otras respuestas)
bytecode77
3
Después de que se lance C # 7, será posible hacer esto: msdn.microsoft.com/en-us/magazine/mt595758.aspx
Burak Karakuş el
11
La Q 'tiene c # 4 como etiqueta, por lo que, aunque esta respuesta es breve, sigue siendo correcta.
Steve Drake
33

Aquí hay una versión demasiado complicada de lo que está preguntando:

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 qué no simplemente hacer una clase?

scottm
fuente
2
sería struct mejor en este caso en lugar de Class?
deathrace
55
La ligera ventaja que veo de esto es que implementa automáticamente el operador igual, verificando que 2 instancias son iguales si los elementos son todos iguales.
JSoet
8
Otro inconveniente de este enfoque es que Item1 e Item2 siguen siendo propiedades públicas en MyTuple
RJFalconer
3
@deathrace Tuple son clases, por lo que si quieres heredar directamente Tuple<T, T2>no puedes ser una estructura.
Chakrava
3
Puedo estar equivocado, pero en su mayoría uso tuplas donde quiero devolver un objeto pero no quiero definir una clase específica ..
Jay
12

Con .net 4 quizás podría mirar ExpandoObject, sin embargo, no lo use para este caso simple, ya que lo que habrían sido errores en tiempo de compilación se convierten en errores en tiempo de ejecución.

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.

Algo más que vale la pena mencionar es un tipo anónimo dentro de un método , pero debe crear una clase si desea devolverlo.

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

Reproduciendo mi respuesta de esto publicación, ya que encaja mejor aquí.

A partir de C # v7.0, ahora es posible asignar un nombre a las propiedades de tupla que antes se usaban de forma predeterminada para nombres predefinidos como Item1, Item2etc.

Nombrando las propiedades de Tuple Literals :

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

La salida en la consola:

Nombre - RBT_Yoga, Edad - 22, Pasión - Dosa

Devolver Tuple (que tiene propiedades con nombre) de un 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);
}

La salida en la consola:

Detalles del empleado: Rasik, Bihari, Rasik-PC, 1000

Crear una lista de tuplas con propiedades con nombre

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

Salida en consola:

1 - vaca 5 - pollos 1 - avión

Espero haberlo cubierto todo. En caso de que haya algo que me haya perdido, por favor envíeme un comentario en los comentarios.

Nota : Mis fragmentos de código utilizan la función de interpolación de cadenas de C # v7 como se detalla aquí .

RBT
fuente
3

MichaelMocko Respondido es genial,

pero quiero agregar algunas cosas que tuve que resolver

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

la línea anterior le dará un error de tiempo de compilación si está usando .net framework <4.7

Entonces, si tiene un proyecto que está usando .net framework <4.7 y aún desea usar ValueTuple, entonces workAround estaría instalando este paquete nuget

Mihir Dave
fuente
2

No, no puedes nombrar a los miembros de la tupla.

Lo intermedio sería usar ExpandoObject en lugar de Tuple.

Joe Mancuso
fuente
2

Si los tipos de sus artículos son todos diferentes, aquí hay una clase que hice para obtenerlos de manera más intuitiva.

El uso de esta clase:

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 fuente:

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
fuente
44
Esto parece mucho trabajo por poco o ningún beneficio. Tiene una limitación no intuitiva (no hay tipos duplicados), y creo que la idea de recuperar un valor solo por su tipo es increíblemente poco intuitiva y no puedo pensar en un caso de uso práctico para ello. Esto es equivalente a hacer una tabla de datos para los empleados, luego decidir recuperar a los empleados por su nombre (en lugar de una clave única) y, posteriormente, exigir que todos los empleados tengan nombres diferentes. Esto no es una solución a un problema, está usando una solución a costa de crear un problema adicional.
Flater
Y que Dios tenga piedad de tu alma.
Jamie M.
1

Esto es muy molesto y espero que las futuras versiones de C # aborden esta necesidad. Creo que el trabajo más fácil es usar un tipo de estructura de datos diferente o cambiar el nombre de los "elementos" por su cordura y por la cordura de otros que leen su código.

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

Creo que crearía una clase pero otra alternativa son los parámetros de salida.

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

Como su Tupla solo contiene números enteros, puede representarlo con un Dictionary<string,int>

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

pero tampoco lo recomiendo.

Jonas Elfström
fuente
0

¿Por qué todos hacen la vida tan difícil? Las tuplas son para el procesamiento de datos bastante temporal . Trabajar con Tuples todo el tiempo hará que el código sea muy difícil de entender en algún momento. Creando clases para todo podría eventualmente inflar su proyecto.

Se trata de equilibrio, sin embargo ...

Su problema parece ser algo para lo que desearía una clase. Y solo por completar, esta clase a continuación también contiene constructores.


Este es el patrón apropiado para

  • Un tipo de datos personalizado
    • sin más funcionalidades. Los getters y setters también se pueden expandir con código, obteniendo / configurando miembros privados con el patrón de nombre de "_orderGroupId", al tiempo que se ejecuta código funcional.
  • Incluyendo constructores. También puede optar por incluir solo un constructor si todos propiedades son obligatorias.
  • Si desea utilizar todos los constructores, burbujear así es el patrón adecuado para evitar el 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;
    }
}

O, si lo desea realmente simple: también puede 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
fuente
0

Escribiría los nombres de los Elementos en el resumen ... así que al pasar el cursor sobre la función helloworld () el texto dirá hola = Elemento1 y mundo = Elemento2

 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
fuente
0

Solo para agregar a la respuesta @MichaelMocko. Las tuplas tienen un par de trampas en este momento:

No puede usarlos en árboles de expresión EF

Ejemplo:

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

Esto no se compilará con el error "Un árbol de expresiones no puede contener un literal de tupla". Desafortunadamente, la API de árboles de expresión no se expandió con soporte para tuplas cuando se agregaron al lenguaje.

Rastree (y vote) este problema para las actualizaciones: https://github.com/dotnet/roslyn/issues/12897

Para solucionar el problema, primero puede convertirlo a tipo anónimo y luego convertir el valor a 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();
}

Otra opción es 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();
}

Referencias

No puedes deconstruirlos en lambdas

Hay una propuesta para agregar el soporte: https://github.com/dotnet/csharplang/issues/258

Ejemplo:

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

Referencias

No se serializarán bien

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

Los nombres de campo de tupla solo están disponibles en tiempo de compilación y se borran por completo en tiempo de ejecución.

Referencias

Eugene Kulabuhov
fuente
-1

Puedes escribir una clase que contenga la Tupla.

Debe anular las funciones Equals y GetHashCode

y los operadores == y! =.

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

volverá:

Cierto

Cierto

ss
fuente
2
Si ya implementó una clase para este tipo de datos, ¿por qué declara una Tupla para los datos subyacentes en lugar de solo propiedades?
bytecode77
Quiero usar el atributo de tupla que corresponde por valor en la función Equals
ss
Eso puede ser un bono. Pero, por otro lado, básicamente creó una clase con propiedades que van desde Item1 a ItemX. Elegiría un nombre apropiado y más código en Equals () en lugar de usar una tupla.
bytecode77
-1

Ejemplo 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
fuente