C # Общая перегрузка List <T>: Как это будет сделано? - PullRequest
8 голосов
/ 01 октября 2010

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

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

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

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

Ответы [ 7 ]

13 голосов
/ 01 октября 2010

Если вы хотите сохранить то же имя для метода 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");
5 голосов
/ 01 октября 2010

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

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!
    }
}
2 голосов
/ 01 октября 2010
public static IList<T> Anything-not-Add*<T>(this IList<T> list, T item)
{
    list.Add(item);
    return list;
}

* AddItem, Append, AppendList и т. Д. (См. Комментарии ниже)

Эта идея пришла мне в голову, как и другим ребятам, независимо:1008 *

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.

1 голос
/ 01 октября 2010

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

Запись lst.Add (obj) в вашем коде бесплатна, ссылка lst доступна в регистре процессора.

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

1 голос
/ 01 октября 2010

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

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);
1 голос
/ 01 октября 2010

Отключите метод расширения:

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

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

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

0 голосов
/ 01 октября 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...