C# List的泛型重载: 这要怎么做呢?

发布于 2024-09-25 21:51:05 字数 370 浏览 5 评论 0原文

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

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

另一方面,List 类的 .Add() 方法手,返回 void - 所以链接调用不起作用。在我看来,这和杰恩·科布的不朽名言“毫无意义”。

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

The StringBuilder class allows you, in what I consider to be a very intuitive way, to chain method calls to .Append(), .AppendFormat() and some others like so:

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

The List class' .Add() method, on the other hand, returns void - so chaining calls doesn't work. This, in my opinion and the immortal words of Jayne Cobb "just don' make no kinda sense".

I admit that my understanding of Generics is very basic, but I would like to overload the .Add() method (and others) so that they return the original object, and allow chaining. Any and all assistance will be rewarded with further Firefly quotes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

甜是你 2024-10-02 21:51:05

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

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

但是,只有当您使用显式键入为 < 的变量来操作列表时,这才有效。 code>MyList(例如,如果您的变量声明为 IList,则它将不起作用)。所以我认为涉及扩展方法的解决方案更好,即使这意味着更改方法的名称。

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

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

If you want to keep the same name for the Add method, you could hide the method from the base class:

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

However, this will only work if you're manipulating the list with a variable explicitly typed as MyList<T> (i.e. it won't work if your variable is declared as IList<T> for instance). So I think the solutions involving an extension method are better, even if that means changing the name of the method.

Although others have already posted solutions with extension methods, here's another one, that has the advantage of conserving the actual type of the collection:

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

Use it like that:

var list = new List<string>();
list.Append("Hello").Append("World");
爱已欠费 2024-10-02 21:51:05

使用可以创建扩展方法

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

use can create extension method

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

we can also make this method generic over collection parameter

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:
Maybe someone will find this trick useful, it is possible to utilize nested object initializer and collection initializer for setting properties and adding values into existing instances.

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!
    }
}
物价感观 2024-10-02 21:51:05
public static IList<T> Anything-not-Add*<T>(this IList<T> list, T item)
{
    list.Add(item);
    return list;
}

* AddItemAppendAppendList 等(参见下面的评论)

我的脑海中浮现出同样的想法其他人也独立地:

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

}

Thomas 是对的:只要 IList 继承 ICollection 你应该使用 ICollection。

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

* AddItem, Append, AppendList, etc. (see comments below)

The same idea came to my mind like other guys' too, independently:

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

}

And Thomas is right: as far as IList<T> inherits ICollection<T> you should use ICollection.

再浓的妆也掩不了殇 2024-10-02 21:51:05

关闭扩展方法:

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

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

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

Have an extension method off:

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

Note that we have to create it with a new name, as if an instance member matches the signature (the 'Add' you are already complaining about) then the extension method won't be called.

In all though, I'd recommend against this. While I like chaining myself, it's being rare in C# libraries means it's not as idiomatic as it is in other languages where it's more common (no technical reason for this, though some differences in how properties work encourages it a bit more in some other languages, just the way things are in terms of what is common). Because of this, the constructs it enables aren't as familiar in C# as elsewhere, and your code is more likely to be misread by another dev.

半衾梦 2024-10-02 21:51:05

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

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

You could use an extension method with a different name:

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

To create code like this:

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

To retain the name Add, however, you can have a method like this:

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

And create code like this:

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

It doesn't look that good though.

Maybe if we change the type we can have a better syntax.

Given this class:

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

You can have this method:

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

And use code like this:

list.Edit().Add(1).Add(2).Add(3);
嗼ふ静 2024-10-02 21:51:05

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

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

返回引用的 Add() 版本使代码速度几乎慢了 5%。对于所提出的扩展方法来说,情况要糟糕得多,因为涉及到整个额外的堆栈帧。

I'm sure you won't appreciate this answer but there's a very good reason that List<>.Add() works this way. It is very fast, it needs to be to be competitive with an array and because it is such a low-level method. It is however just a hair too big to get inlined by the JIT optimizer. It cannot optimize the return statement you'd need to return the list reference.

Writing lst.Add(obj) in your code is for free, the lst reference is available in a CPU register.

A version of Add() that returns the reference makes the code almost 5% slower. It's a lot worse for the proposed extension method, there an entire extra stack frame involved.

逆夏时光 2024-10-02 21:51:05

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

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

做这样的事情会达不到你所需要的吗?

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

I like the extension approach that others have mentioned as that seems to answer the question well (although you would have to give it a different method signature than the existing Add()). Also, it does seem like there's some inconsistency about object returns on calls like this (I thought it was a mutability issue, but the stringbuilder is mutable isn't it?), so you raise an interesting question.

I'm curious, though, if the AddRange method would not work as an out-of-the-box solution? Is there a particular reason you want to chain the commands instead of passing everything in as a an array?

Would do something like this not accomplish what you need?

List<string> list = new List<string>();
list.AddRange(new string[]{
    "first string", 
    "second string",
});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文