C# 泛型重载 List<T> :这将如何完成?

Posted

技术标签:

【中文标题】C# 泛型重载 List<T> :这将如何完成?【英文标题】:C# Generic overloading of List<T> : How would this be done? 【发布时间】:2011-04-19 23:20:42 【问题描述】:

StringBuilder 类允许您以我认为非常直观的方式将方法调用链接到 .Append()、.AppendFormat() 和其他一些类似的方法:

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

另一方面,List 类的 .Add() 方法返回 void - 因此链接调用不起作用。在我看来,这和 Jayne Cobb 的不朽话语“完全没有意义”。

我承认我对泛型的理解非常基础,但我想重载 .Add() 方法(和其他方法),以便它们返回原始对象,并允许链接。任何和所有帮助都将获得更多 Firefly 报价。

【问题讨论】:

【参考方案1】:

如果您想为 Add 方法保留相同的名称,您可以从基类中隐藏该方法:

public class MyList<T> : List<T>

    public new MyList<T> Add(T item)
    
        base.Add(item);
        return this;
    

但是,这仅在您使用显式类型为 MyList&lt;T&gt; 的变量操作列表时才有效(例如,如果您的变量被声明为 IList&lt;T&gt;,它将不起作用)。所以我认为涉及扩展方法的解决方案更好,即使这意味着更改方法的名称。

虽然其他人已经发布了带有扩展方法的解决方案,但这里有另一个,它的优点是保留了集合的实际类型:

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】:

使用可以创建扩展方法

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!
    

【讨论】:

不改方法名有没有办法做到这一点?我想尽量减少其他开发人员可能产生的困惑 您应该将参数声明为 ICollection 而不是 List @Thomas: Add() 是 IList 和 ICollection 的方法 @desco IList 也是如此,这就是为什么我将答案更改为处理并返回 List。 @ScottSEA 如果您可以更改方法的机制但不能更改其名称,那么这真的会让其他开发人员感到困惑。事实上,我已经避免了整个链接方法,因为它在 C# 中不像其他一些语言那样惯用,因此很容易引起混淆(事实上,因为有些人反对链接,因为它可以把人们绊倒到它所在的地方和不允许的地方)。 @abatishchev,是的,但是 IList 继承了 ICollection。您应该尽可能使用派生较少的类型【参考方案3】:
public static IList<T> Anything-not-Add*<T>(this IList<T> list, T item)

    list.Add(item);
    return list;

*AddItemAppendAppendList等(见下方的cmets)

我也像其他人一样独立地想到了同样的想法:

public static TList Anything<TList, TItem>(this TList list, TItem item)
    where TList : IList<TItem>

    list.Add(item);
    return list;

Thomas 是对的:就IList&lt;T&gt; 继承ICollection&lt;T&gt; 而言,您应该使用ICollection。

【讨论】:

取胜的扩展方法!这仅适用于 C# 3 或更高版本(因此 VS 2008 或 2010)。 这不起作用,因为在重载解析期间编译器会更喜欢实例方法而不是扩展 这是一种扩展方法吧?编译器如何知道使用这个版本的方法而不是原来的(@desco 似乎在说它不会)【参考方案4】:

关闭扩展方法:

public static List<T> Append(this List<T> list, T item)

  list.Add(item);
  return self;

请注意,我们必须使用新名称创建它,就好像实例成员与签名匹配(您已经在抱怨的“添加”),然后扩展方法将不会被调用。

不过,我建议不要这样做。虽然我喜欢链接自己,但它在 C# 库中很少见,这意味着它不像在其他更常见的语言中那样惯用(没有技术原因,尽管属性工作方式的一些差异在其他一些语言中鼓励它更多一些) ,就像事情的共同点一样)。因此,它所支持的结构在 C# 中不像在其他地方那样熟悉,而且您的代码更有可能被其他开发人员误读。

【讨论】:

【参考方案5】:

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

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】:

我相信您不会欣赏这个答案,但 List.Add() 以这种方式工作是有充分理由的。它非常快,需要与数组竞争,因为它是一种低级方法。然而,它只是一根头发太大而无法被 JIT 优化器内联。它无法优化您需要返回列表引用的 return 语句。

在您的代码中编写 lst.Add(obj) 是免费的,lst 引用在 CPU 寄存器中可用。

返回引用的 Add() 版本使代码慢了近 5%。建议的扩展方法更糟糕,涉及整个额外的堆栈帧。

【讨论】:

我相信你不会欣赏这个评论,但也许你可以解释为什么我不会欣赏这个答案。它写得很好,易于理解,并提出了一些关于优化的要点。【参考方案7】:

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

不过,我很好奇,AddRange 方法是否不能作为开箱即用的解决方案?您是否有特殊原因要链接命令而不是将所有内容作为数组传递?

做这样的事情不能满足你的需要吗?

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

【讨论】:

以上是关于C# 泛型重载 List<T> :这将如何完成?的主要内容,如果未能解决你的问题,请参考以下文章

C#方法重载和泛型接口

C# - 定义泛型函数重载的首选项

C#中的泛型和泛型集合

C# 中的多态、重载和泛型

C#泛型列表<T>如何获取T的类型? [复制]

C# 将Object类转化为List<T>类,其中T的类型不是object类型。