代码之家  ›  专栏  ›  技术社区  ›  Scott Baker

列表的泛型重载:如何实现?

  •  8
  • Scott Baker  · 技术社区  · 15 年前

    StringBuilder类允许您将方法调用链接到.Append()、.AppendFormat()和其他类似的方法:

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

    我承认我对泛型的理解是非常基本的,但是我希望重载.Add()方法(和其他方法),以便它们返回原始对象,并允许链接。任何和所有的援助将奖励进一步萤火虫报价。

    7 回复  |  直到 15 年前
        1
  •  14
  •   Thomas Levesque    15 年前

    如果您想为 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");
    
        2
  •  5
  •   desco    15 年前

    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!
        }
    }
    
        3
  •  2
  •   abatishchev Karl Johan    15 年前
    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>

        4
  •  1
  •   Jon Hanna    15 年前

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

    注意,我们必须用一个新名称来创建它,就像一个实例成员与签名(您已经在抱怨的“Add”)匹配,那么就不会调用扩展方法。

    不过,我还是建议你不要这样。虽然我喜欢自己链接,但在C库中这种情况很少见,这意味着它不像其他语言那样惯用(这不是技术原因,尽管属性如何工作的一些差异在某些其他语言中更容易引起这种情况,正如事物在公共性方面的方式一样)。因此,它所支持的构造在C中不像其他地方那么熟悉,而且您的代码更容易被其他开发人员误读。

        5
  •  1
  •   Jordão    15 年前

    您可以使用具有不同名称的扩展方法:

    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);
    
        6
  •  1
  •   Hans Passant    15 年前

    我相信您不会喜欢这个答案,但是List<.Add()是这样工作的,这是一个很好的理由。它非常快,需要与数组竞争,因为它是一种低级方法。不过,这只是一根头发太大了,无法被JIT优化器内联。它无法优化您需要返回列表引用的return语句。

    返回引用的Add()版本使代码速度降低了近5%。对于所提出的扩展方法来说,这要糟糕得多,它涉及到整个额外的堆栈帧。

        7
  •  0
  •   Steven    15 年前

    我喜欢其他人提到的扩展方法,因为它似乎很好地回答了这个问题(尽管您必须给它一个不同于现有Add()的方法签名)。而且,在这样的调用中,对象返回似乎确实存在一些不一致之处(我原以为这是一个可变的问题,但stringbuilder是可变的,不是吗?),所以你提出了一个有趣的问题。

    不过,我很好奇AddRange方法是否不能作为现成的解决方案工作?有没有什么特别的原因让你想把命令串起来,而不是把所有的东西都作为一个数组传入?

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