在我使用的大多数方法中,返回某种集合,我返回IEnumerable而不是特定类型(例如List)。在许多情况下,我有另一个我想要与结果IEnumerable结合的集合,这就像使用List并使用AddRange方法向其添加另一个List一样。我有以下示例,其中我创建了一个扩展方法,该方法应该将一组项添加并添加到基本集合中,而调试它似乎有效,但在原始集合中,项永远不会添加。我不明白这一点,为什么他们没有添加,是否有关于IEnumerable的实现,我缺少什么?我知道IEnumerable是一个只读接口,但我没有在下面的例子中添加到这个列表,我正在替换它,但原来的IEnumerable没有改变。
class Program
{
static void Main(string[] args)
{
var collectionOne = new CollectionContainerOne();
var collectionTwo = new CollectionContainerTwo();
// Starts at 1- 50 //
collectionOne.Orders.AddRange(collectionTwo.Orders);
// Should now be 100 items but remains original 50 //
}
}
public class CollectionContainerOne
{
public IEnumerable<Order> Orders { get; set; }
public CollectionContainerOne()
{
var testIds = Enumerable.Range(1, 50);
var orders = new List<Order>();
foreach (int i in testIds)
{
orders.Add(new Order() { Id = i, Name = "Order #" + i.ToString() });
}
this.Orders = orders;
}
}
public class CollectionContainerTwo
{
public IEnumerable<Order> Orders { get; set; }
public CollectionContainerTwo()
{
var testIds = Enumerable.Range(51, 50);
var orders = new List<Order>();
foreach (int i in testIds)
{
orders.Add(new Order() { Id = i, Name = "Order #" + i.ToString() });
}
this.Orders = orders;
}
}
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}
public static class IEnumerable
{
public static void AddRange<T>(this IEnumerable<T> enumerationToAddTo, IEnumerable<T> itemsToAdd)
{
var addingToList = enumerationToAddTo.ToList();
addingToList.AddRange(itemsToAdd);
// Neither of the following works //
enumerationToAddTo.Concat(addingToList);
// OR
enumerationToAddTo = addingToList;
// OR
enumerationToAddTo = new List<T>(addingToList);
}
}
答案 0 :(得分:4)
您正在修改参数enumerationToAddTo,这是一个参考。但是,引用本身不是通过引用传递的,因此在修改引用时,调用者中的更改是不可观察的。此外,无法在扩展方法中使用ref
参数。
最好使用Enumerable.Concat<T>
。或者,您可以使用ICollection,它具有Add(T)方法。遗憾的是,List<T>.AddRange
未在任何界面中定义。
以下是一个示例,用于说明通过引用传递引用类型。正如尼古拉指出的那样,这不是真正有用的代码。不要在家里试试这个!
void Caller()
{
// think of ss as a piece of paper that tells you where to find the list.
List<string> ss = new List<string> { "a", "b" };
//passing by value: we take another piece of paper and copy the information on ss to that piece of paper; we pass that to the method
DoNotReassign(ss);
//as this point, ss refers to the same list, that now contains { "a", "b", "c" }
//passing by reference: we pass the actual original piece of paper to the method.
Reassign(ref ss);
// now, ss refers to a different list, whose contents are { "x", "y", "z" }
}
void DoNotReassign(List<string> strings)
{
strings.Add("c");
strings = new List<string> { "x", "y", "z" }; // the caller will not see the change of reference
//in the piece of paper analogy, we have erased the piece of paper and written the location
//of the new list on it. Because this piece of paper is a copy of SS, the caller doesn't see the change.
}
void Reassign(ref List<string> strings)
{
strings.Add("d");
//at this point, strings contains { "a", "b", "c", "d" }, but we're about to throw that away:
strings = new List<string> { "x", "y", "z" };
//because strings is a reference to the caller's variable ss, the caller sees the reassignment to a new collection
//in the piece of paper analogy, when we erase the paper and put the new object's
//location on it, the caller sees that, because we are operating on the same
//piece of paper ("ss") as the caller
}
修改强>
考虑这个程序片段:
string originalValue = "Hello, World!";
string workingCopy = originalValue;
workingCopy = workingCopy.Substring(0, workingCopy.Length - 1);
workingCopy = workingCopy + "?";
Console.WriteLine(originalValue.Equals("Hello, World!"); // writes "True"
Console.WriteLine(originalValue.Equals(workingCopy); // writes "False"
如果您对引用类型的假设为真,则输出将为“False”,然后为“True”
答案 1 :(得分:3)
按照以下方式调用您的扩展程序:
collectionOne.Orders.AddRange(collectionTwo.Orders);
基本上与:
相同IEnumerable.AddRange(collectionOne.Orders, collectionTwo.Orders);
现在发生了什么,是您将参考副本传递给collectionOne.Orders
到AddRange
方法。在AddRange
实现中,您尝试为副本分配新值。内部“迷失”。您未向<{1}}分配新值,您将其分配给其本地副本 - 该范围仅在方法正文中。由于所有修改都发生在内部 collectionOne.Orders
,外界注意到没有任何变化。
您需要返回 new 可枚举,或直接在列表上工作。在AddRange
上使用变异方法相当违反直觉,我会远离这样做。
答案 2 :(得分:3)
您想要什么,称为Concat
。基本上,当你在Main中执行此操作时:
var combined = collectionOne.Orders.Concat(collectionTwo.Orders);
这里,combined
将引用一个IEnumerable,它将在枚举时遍历两个源集合。
答案 3 :(得分:1)
IEnumerable不支持添加。您在代码中实际上做的是从您的枚举中创建新集合,并将项目添加到该新集合中。您的旧系列仍有相同的商品。
例如,您创建了一个这样的数字集合
Collection1 = [ 1, 2, 3, 4, 5 ]
当你做Collection1.ToList()。添加(...)你将获得具有相同成员的新集合,并添加如下所示的新成员:
Collection1 = [ 1, 2, 3, 4, 5, 6, 7, ... ]
然而,旧的集合仍将保留旧成员,因为ToList会创建新的集合。
不使用IEnumerable,而是使用支持修改的IList。
将IEnumerable转换回它的派生类型并向其添加成员。这很糟糕,事实上最好只是首先返回List
IEnumerable<Order> collectionOne = ...;
List<Order> collectionOneList = (List<Order>)collectionOne;
collectionOneList.Add(new Order());
如果要返回.NET中标准的集合,则没有理由返回其接口。在这种情况下,最好使用原始类型。如果您正在返回自己实现的集合,那么您应该返回一个接口 当您考虑输入参数时,这是一个完全不同的情况。如果您的方法要求枚举项目,那么您应该要求IEnumerable。通过这种方式,您可以通过它完成所需的操作,并且您对调用它的人的限制最少。他们可以发送任何可枚举的。如果需要添加到该集合,则可能需要IList,以便您也可以在方法中对其进行修改。
答案 4 :(得分:0)
基本上问题是你不能部分地为enumerationToAddTo
赋值,因为它不是引用参数。同样,当phoog提及ToList()
时会创建一个新列表,并且不会将现有的IEnumerable转换为列表。
这不是一个很好的扩展使用。我建议您在容器集合中添加一个方法,允许您向IEnumerable实例添加新项目。这样可以更好地封装特定于该类的逻辑。