C# Generic overloading of List: как это сделать?


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

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

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

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

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

Use может создать метод расширения

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

EDIT

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

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: Возможно, кто-то найдет этот трюк полезным, можно использовать вложенный инициализатор объекта и инициализатор коллекции для установки свойств и добавления значений в существующие экземпляры.

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, и т.д. (см. комментарии ниже)

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

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

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

В целом, я бы рекомендовал против этого. Хотя мне нравится цеплять себя, это редкость в библиотеках 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 доступна в регистре процессора.

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

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

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

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

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