IEnumerable 作为构造函数参数,当列表保留在类中时

IEnumerable as constructor parameter when list is kept in class

提问人:gartenriese 提问时间:4/8/2023 最后编辑:Bill Tür stands with Ukrainegartenriese 更新时间:4/8/2023 访问量:157

问:

如果我想将类作为参数传递的列表存储,我应该使用哪种参数类型?

我应该参加 IEnumerable 并执行类似操作吗?但是,如何避免多余的副本呢?如果列表是在构造函数调用中创建的,那肯定是太多了。Items = items.ToList().AsReadOnly()ToList()

在 C++ 中,我将有一个非引用参数来确保传递列表的副本,但在 C# 中,AFAIK 这是不可能的。

我想到的一个例子是一个事件参数类,它包含一个内容列表,这也是调用类的当前状态。在将列表传递给各种事件处理程序后,列表不应更改。

在构造函数中创建一个副本是可以的,当它被调用时,就像这样:

SomethingHappened?.Invoke(this, new CustomEventArgs(_items));

但是当它被这样调用时,会制作一个不必要的副本:

SomethingHappened?.Invoke(this, new CustomEventArgs(new List { item1, item2 }));
C# 列表 参数传递

评论

2赞 Julian 4/8/2023
您的班级可能不应该知道它正在处理副本。您的班级对列表的处理方式取决于您的班级。你的目标是什么?例如,如果类不需要更改列表,则可以编写一个构造函数,该构造函数通过调用原始 List 实例将列表传递给构造函数。您将在该类之外拥有原始实例,并且在该类中具有只读引用。IReadOnlyList<T>AsReadOnly()
0赞 gartenriese 4/8/2023
@ewerspej 该类拥有列表的所有权。因此,它需要确保调用类之后不会更改它。
0赞 Julian 4/8/2023
所以它是相反的?我建议您将该信息更明确地添加到问题中。在这种情况下,制作一份副本就足够了。您能详细说明一下这个场景,展示一个代码示例吗?
0赞 gartenriese 4/8/2023
@ewerspej:我加了一个例子。
1赞 Julian 4/8/2023
这两种情况之间几乎没有区别。从构造函数的角度来看,这些语句是一回事。因此,如果想要数据完整性,则必须创建副本,因为无法知道构造函数的调用方式。从技术上讲,您可以通过传入另一个参数来解决这个问题,该参数确定是否应该制作副本,然后将责任(和知识)从被调用者转移到调用者。

答:

1赞 Julian 4/8/2023 #1

如果不想修改在类中传递给构造函数的列表,则可以将参数定义为 an 并通过调用原始列表来传递它。您仍将使用相同的列表,但会提供一个防止修改的包装器,如文档所示:IReadOnlyList<T>AsReadOnly()AsReadOnly()

public class YourClass
{
    private IReadOnlyList<SomeType> yourList;

    public YourClass(IReadOnlyList<SomeType> list)
    {
        yourList = list;
    }
}

然后调用构造函数,如下所示:

var someList = new List<SomeType>();

var yourClassInstance = new YourClass(someList.AsReadOnly());

您仍然可以更改原始列表引用,它将反映在只读引用中,因为它们实际上访问的是同一个列表,但您将无法修改其中的列表。YourClass

或者,如果不能在外部调用,则需要在构造函数中调用它,这在技术上允许类在存储只读引用之前进行修改:AsReadOnly()

public class YourClass
{
    private IReadOnlyList<SomeType> yourList;

    public YourClass(IList<SomeType> list)
    {
        yourList = list.AsReadOnly();
    }
}

如果您确实需要修改传递的列表,则需要创建一个副本,然后通过该副本获得副本的所有权:YourClass

public class YourClass
{
    private IList<SomeType> yourList;

    public YourClass(IList<SomeType> list)
    {
        yourList = new List<T>(list);
    }
}

由于被调用方无法自行知道是否应创建副本,因此可以向构造函数添加一个可选标志,以将责任转移给调用方:

public class YourClass
{
    private IList<SomeType> yourList;

    public YourClass(IList<SomeType> list, bool makeCopy = false)
    {
        yourList = makeCopy ? new List<T>(list) : list;
    }
}

这样,调用方必须决定是否需要副本:

var someList = new List<SomeType>();

//Make a copy explicitly
var yourClassInstance1 = new YourClass(someList, true);

//Do not make a copy
var yourClassInstance2 = new YourClass(new List<SomeType>{ object1, object2 });

评论

0赞 gartenriese 4/8/2023
谢谢,我希望会有更好的解决方案,但我想我只是被使用 C++ 宠坏了。
1赞 Julian 4/8/2023
@gartenriese我不知道针对类对象的这个特定问题有另一种解决方案。没有参数(在 C# 中,关键字是编译时关键字),也不能通过将引用类型实例作为值传递来隐式创建引用类型实例的副本。这仅适用于值类型,例如结构。constconst