我需要添加一个类的ICollection<string>
属性,其中我有一个IEnumerable
。这是一个完整的程序,说明了问题:
using System;
using System.Collections.Generic;
using System.Linq;
namespace CollectionAddingTest
{
public class OppDocumentServiceResult
{
public OppDocumentServiceResult()
{
this.Reasons = new List<string>();
}
public Document Document { get; set; }
public bool CanBeCompleted
{
get
{
return !Reasons.Any();
}
}
public ICollection<string> Reasons { get; private set; }
}
public class Document
{
public virtual string Name { get; set; }
}
public class Program
{
private static void Main(string[] args)
{
var docnames = new List<string>(new[] {"test", "test2"});
var oppDocResult = docnames
.Select(docName
=> new OppDocumentServiceResult
{
Document = new Document { Name = docName }
});
foreach (var result in oppDocResult)
{
result.Document.Name = "works?";
result.Reasons.Add("does not stick");
result.Reasons.Add("still does not stick");
}
foreach (var result in oppDocResult)
{
// doesn't write "works?"
Console.WriteLine(result.Document.Name);
foreach (var reason in result.Reasons)
{
// doesn't even get here
Console.WriteLine("\t{0}", reason);
}
}
}
}
}
我希望每个OppDocumentServiceResult都有引用的Document.Name
属性设置为工作?并且每个OppDocumentServiceResult应该有两个原因添加到它。然而,两者都没有发生。
我无法添加内容的Reasons属性有什么特别之处?
答案 0 :(得分:2)
问题是您初始Select
您正在实例化新的OppDocumentServiceResult
个对象。添加一个ToList
,你应该很高兴:
var oppDocResult = docnames
.Select(docName
=> new OppDocumentServiceResult
{
Document = new Document { Name = docName }
}).ToList();
正如Servy指出的那样,我应该在答案中添加更多细节,但幸运的是the comment他留在Tallmaris' answer处理这个问题。在他的回答中,Jon Skeet进一步扩展了原因,但它归结为“是oppDocResult是LINQ查询的结果,使用延迟执行。”
答案 1 :(得分:2)
问题是oppDocResult
是使用延迟执行的LINQ查询的结果。
换句话说,每次迭代它时,查询都会执行,并且会创建 new OppDocumentServiceResult
个对象。如果您在OppDocumentServiceResult
构造函数中放置诊断程序,您将看到它。
因此,您在最后迭代的OppDocumentServiceResult
个对象与您添加原因的对象不同。
现在,如果您添加ToList()
电话,那么将查询具体化到“普通”集合(List<OppDocumentServiceResult>
)。每次迭代该列表时,它都会返回对相同对象的引用 - 所以如果你第一次迭代它们时添加原因,那么当你再次遍历它们时打印出原因,你会得到结果你正在寻找。
有关更多详细信息,请参阅this blog post(在“LINQ延期执行”的许多搜索结果中)。
答案 2 :(得分:1)
ForEach()
仅针对List<T>
进行定义,您 无法将其用于ICollection<T>
。
你必须选择:
((List<string>) Reasons).ForEach(...)
或者
Reasons.ToList().ForEach(...)
然而,我的首选方法
我会定义此扩展程序,它可以帮助您自动执行此操作而不浪费资源:
public static class ICollectionExtensions
{
public static void ForEach(this ICollection<T> collection, Action<T> action)
{
var list = collection as List<T>;
if(list==null)
collection.ToList().ForEach(action);
else
list.ForEach(action);
}
}
现在,我可以ForEach()
使用ICollection<T>
。
答案 3 :(得分:1)
修正了这样,转换为List而不是保留IEnumerable:
var oppDocResult = docnames
.Where(docName => !String.IsNullOrEmpty(docName))
.Select(docName
=> new OppDocumentServiceResult
{
Document = docName
}).ToList();
我只能猜测(这真的是在黑暗中拍摄!)这背后的原因是在IEnumerable中元素就像真实元素的“代理”一样?基本上Linq查询定义的Enumerable就像一个获取所有数据的“承诺”,所以每次迭代你都会得到原始项目?这并不能解释为什么正常的财产仍然坚持......
所以,修复就在那里,但我担心的解释不是......至少不是来自我:(
答案 4 :(得分:0)
只需在课堂内更改代码
public List<string> Reasons { get; private set; }