C# общая перегрузка списка: как это сделать?

класс StringBuilder позволяет вам, что я считаю очень интуитивным способом, связывать вызовы методов .Добавлять. ,)(AppendFormat() и некоторые другие, например:

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

список класса .Метод Add (), с другой стороны, возвращает void - поэтому вызовы цепочки не работают. Это, на мой взгляд, и бессмертные слова Джейн Кобб "просто не имеют никакого смысла".

Я признаю, что мое понимание дженериков очень основное, но я хотел бы перегрузить .Добавьте () метод (и другие), чтобы они возвращали исходный объект и разрешали цепочку. Любая помощь будет вознаграждена дальнейшими цитатами Firefly.

7 ответов


если вы хотите сохранить то же имя Add метод, вы можете скрыть метод из базового класса:

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

однако это будет работать, только если вы манипулируете списком с переменной, явно набранной как MyList<T> (т. е. он не будет работать, если ваша переменная объявлена как IList<T> например). Поэтому я думаю, что решения, связанные с методом расширения, лучше, даже если это означает изменение имени метода.

хотя другие уже выложили решения с методами расширения, вот еще один, который имеет преимущество сохранения фактического типа коллекции:

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

используйте его так:

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

использование может создать метод расширения

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

редактировать

мы также можем сделать этот метод универсальным по параметру коллекции

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

Изменить 2: Возможно, кто-то найдет этот трюк полезным, можно использовать инициализатор вложенных объектов и инициализатор коллекции для установки свойств и добавления значений в существующие экземпляры.

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

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

* AddItem, Append, AppendList, etc. (см. комментарии ниже)

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

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

}

и Томас прав: насколько IList<T> наследует ICollection<T> вы должны использовать ICollection.


есть метод расширения off:

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

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

в целом, я бы рекомендовал против этого. Хотя мне нравится цеплять себя, это редкость в библиотеках c# означает, что это не так идиоматично, как на других языках, где это более распространено (нет технической причины для этого, хотя некоторые различия в том, как работают свойства, поощряют его немного больше на некоторых других языках, так же, как и с точки зрения того, что является общим). Из-за этого конструкции, которые он включает, не так знакомы в C#, как в других местах, и ваш код, скорее всего, будет неправильно истолкован другим разработчиком.


вы можете использовать метод расширения с другим именем:

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

создать такой код:

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

сохранить имя Add, однако, у вас может быть такой метод:

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

и создайте такой код:

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

это не выглядит так уж хорошо.

возможно, если мы изменим тип, у нас будет лучший синтаксис.

дали этот класс:

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

вы можете иметь это метод:

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

и используйте такой код:

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

Я уверен, что вы не оцените этот ответ, но есть очень веская причина, что список.Add () работает таким образом. Это очень быстро, он должен быть конкурентоспособным с массивом и потому, что это такой низкоуровневый метод. Однако это просто слишком большой волос, чтобы получить встроенный оптимизатор JIT. Он не может оптимизировать инструкцию return, необходимую для возврата ссылки на список.

написание lst.Добавить (obj) в ваш код бесплатно, ссылка lst доступна в CPU реестр.

версия Add (), которая возвращает ссылку, делает код почти на 5% медленнее. Это намного хуже для предлагаемого метода расширения, там задействован весь дополнительный кадр стека.


Мне нравится подход расширения, который другие упоминали, поскольку это, похоже, хорошо отвечает на вопрос (хотя вам придется дать ему другую подпись метода, чем существующее Add()). Кроме того, похоже, что есть некоторая несогласованность в возвратах объекта при таких вызовах (я думал, что это проблема изменчивости, но stringbuilder изменчив, не так ли?), поэтому вы задаете интересный вопрос.

Мне любопытно, хотя, если метод AddRange не будет работать как из-из-коробки? Есть ли конкретная причина, по которой вы хотите связать команды, а не передавать все как массив?

сделал бы что-то подобное, не выполнив то, что вам нужно?

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