Pregunta C # Sobrecarga genérica de List : ¿Cómo se haría esto?


La clase StringBuilder te permite, en lo que considero que es una forma muy intuitiva, encadenar las llamadas a los métodos a .Append (), .AppendFormat () y algunos más, así:

StringBuilder sb = new StringBuilder();
sb.Append("first string")
  .Append("second string);

El método de la clase List '.Add (), por el contrario, devuelve vacío, por lo que el encadenamiento de llamadas no funciona. Esto, en mi opinión y las palabras inmortales de Jayne Cobb "simplemente no tienen sentido".

Admito que mi comprensión de Generics es muy básica, pero me gustaría sobrecargar el método .Add () (y otros) para que devuelvan el objeto original y permitan el encadenamiento. Todos y cada uno de los asistentes serán recompensados ​​con más cotizaciones de Firefly.


8
2017-10-01 16:55


origen


Respuestas:


Si quieres mantener el mismo nombre para el Add método, podría ocultar el método de la clase base:

public class MyList<T> : List<T>
{
    public new MyList<T> Add(T item)
    {
        base.Add(item);
        return this;
    }
}

Sin embargo, esto solo funcionará si está manipulando la lista con una variable escrita explícitamente como MyList<T> (es decir, no funcionará si su variable se declara como IList<T> por ejemplo). Por lo tanto, creo que las soluciones que implican un método de extensión son mejores, incluso si eso significa cambiar el nombre del método.

Aunque otros ya han publicado soluciones con métodos de extensión, aquí hay otra, que tiene la ventaja de conservar el tipo real de la colección:

public static class ExtensionMethods
{
    public static TCollection Append<TCollection, TItem>(this TCollection collection, TItem item)
        where TCollection : ICollection<TItem>
    {
        collection.Add(item);
        return collection;
    }
}

Úselo así:

var list = new List<string>();
list.Append("Hello").Append("World");

13
2017-10-01 17:07



el uso puede crear un método de extensión

public static class ListExtensions
{
    public static List<T> AddItem<T>(this List<T> self, T item)
    {
        self.Add(item);
        return self;
    }
}

var l = new List<int>();
l.AddItem(1).AddItem(2);

EDITAR

también podemos hacer que este método sea genérico a través del parámetro de recopilación

public static class ListExtensions
{   
    public static TC AddItem<TC, T>(this TC self, T item)
        where TC : ICollection<T>
    {
        self.Add(item);
        return self;
    }
}

var c1 = new Collection<int>();
c1.AddItem(1).AddItem(2);

var c2 = new List<int>();
c2.AddItem(10).AddItem(20);

EDIT 2: Tal vez alguien encuentre útil este truco, es posible utilizar el inicializador de objetos anidados y el inicializador de colecciones para establecer propiedades y agregar valores a las instancias existentes.

using System;
using System.Collections.Generic;
using System.Linq;

struct I<T>
{
    public readonly T V;
    public I(T v)
    {
        V = v;
    }
}

class Obj
{
    public int A { get; set; }
    public string B { get; set; }

    public override string ToString()
    {
        return string.Format("A={0}, B={1}", A, B);
    }
}


class Program
{
    static void Main()
    {
        var list = new List<int> { 100 };
        new I<List<int>>(list)
            {
                V = { 1, 2, 3, 4, 5, 6 }
            };

        Console.WriteLine(string.Join(" ", list.Select(x => x.ToString()).ToArray())); // 100 1 2 3 4 5 6 

        var obj = new Obj { A = 10, B = "!!!" };
        Console.WriteLine(obj); // A=10, B=!!!
        new I<Obj>(obj)
            {
                V = { B = "Changed!" }
            };
        Console.WriteLine(obj); // A=10, B=Changed!
    }
}

5
2017-10-01 16:58



public static IList<T> Anything-not-Add*<T>(this IList<T> list, T item)
{
    list.Add(item);
    return list;
}

*  AddItem, Append, AppendList, etc. (ver comentarios a continuación)

La misma idea vino a mi mente como la de otros muchachos también, independientemente:

public static TList Anything<TList, TItem>(this TList list, TItem item)
    where TList : IList<TItem>
{
    list.Add(item);
    return list;

}

Y Thomas tiene razón: en cuanto a IList<T> hereda ICollection<T> deberías usar ICollection.


2
2017-10-01 16:56



Tiene un método de extensión desactivado:

public static List<T> Append(this List<T> list, T item)
{
  list.Add(item);
  return self;
}

Tenga en cuenta que tenemos que crearlo con un nuevo nombre, como si un miembro de la instancia coincide con la firma (el 'Agregar' del que ya se está quejando), entonces no se llamará al método de extensión.

En todo caso, recomendaría no hacerlo. Aunque me gusta encadenarme, es raro en las bibliotecas C # significa que no es tan idiomático como en otros idiomas, donde es más común (no hay una razón técnica para esto, aunque algunas diferencias en cómo funcionan las propiedades lo alienta un poco más en algunos otros idiomas , tal como están las cosas en términos de lo que es común). Debido a esto, las construcciones que habilita no son tan familiares en C # como en ningún otro lado, y es más probable que otro desarrollador interprete mal su código.


1
2017-10-01 17:03



Puede usar un método de extensión con un nombre diferente:

public static T Put<T, U>(this T collection, U item) where T : ICollection<U> {
  collection.Add(item);
  return collection;
}

Para crear código como este:

var list = new List<int>();
list.Put(1).Put(2).Put(3);

Para retener el nombre AddSin embargo, puedes tener un método como este:

public static T Add<T, U>(this T collection, Func<U> itemProducer) 
  where T : ICollection<U> {
  collection.Add(itemProducer());
  return collection;
}

Y crea un código como este:

list.Add(()=>1).Add(()=>2).Add(()=>3);

Aunque no se ve tan bien.

Tal vez si cambiamos el tipo podemos tener una mejor sintaxis.

Dada esta clase:

public class ListBuilder<T> {
  IList<T> _list;
  public ListBuilder(IList<T> list) {
    _list = list;
  }
  public ListBuilder<T> Add(T item) {
    _list.Add(item);
    return this;
  }
}

Puedes tener este método:

public static ListBuilder<T> Edit<T>(this IList<T> list) {
  return new ListBuilder<T>(list);
}

Y usa un código como este:

list.Edit().Add(1).Add(2).Add(3);

1
2017-10-01 17:50



Estoy seguro de que no apreciarás esta respuesta, pero hay una muy buena razón para que List <>. Add () funcione de esta manera. Es muy rápido, tiene que ser competitivo con una matriz y porque es un método de bajo nivel. Sin embargo, es solo un pelo demasiado grande para ser incorporado por el optimizador JIT. No puede optimizar la declaración de devolución que necesitaría para devolver la referencia de la lista.

Escribir lst.Add (obj) en su código es gratis, la primera referencia está disponible en un registro de la CPU.

Una versión de Add () que devuelve la referencia hace que el código sea casi un 5% más lento. Es mucho peor para el método de extensión propuesto, hay un marco de pila extra involucrado.


1
2017-10-01 18:01



Me gusta el enfoque de extensión que otros han mencionado ya que parece responder bien a la pregunta (aunque tendrías que darle una firma de método diferente a la existente Add ()). Además, parece que hay alguna incoherencia sobre los retornos de objeto en llamadas como esta (pensé que era un problema de mutabilidad, pero el generador de cadenas es mutable ¿no?), Por lo que planteas una pregunta interesante.

Tengo curiosidad, sin embargo, si el método AddRange no funcionara como una solución lista para usar. ¿Hay alguna razón particular por la que quiera encadenar los comandos en lugar de pasar todo como una matriz?

¿Haría algo como esto no lograr lo que necesita?

List<string> list = new List<string>();
list.AddRange(new string[]{
    "first string", 
    "second string",
});

0
2017-10-01 17:23