0

我需要添加到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 都应该添加两个原因。然而,两者都没有发生。

我无法向其添加内容的原因属性有什么特别之处?

4

5 回答 5

2

问题是这oppDocResult是使用延迟执行的 LINQ 查询的结果。

换句话说,每次迭代它时,都会执行查询并创建 OppDocumentServiceResult对象。如果你把诊断放在OppDocumentServiceResult构造函数中,你会看到。

因此,OppDocumentServiceResult您最后迭代的对象与您添加原因的对象不同。

现在,如果您添加一个ToList()调用,那么它将查询具体化为一个“普通”集合(a List<OppDocumentServiceResult>)。每次迭代该列表时,它都会返回对相同对象的引用 - 因此,如果您在第一次迭代它们时添加原因,然后在再次迭代它们时打印出原因,您将得到结果'正在寻找。

有关更多详细信息,请参阅此博客文章(在“LINQ 延迟执行”的许多搜索结果中)。

于 2012-05-15T16:39:54.173 回答
2

问题是您最初Select正在实例化新OppDocumentServiceResult对象。添加一个ToList,你应该很高兴:

var oppDocResult = docnames
    .Select(docName
            => new OppDocumentServiceResult
                   {
                       Document = new Document { Name = docName }
                   }).ToList();

正如Servy指出的那样,我应该在我的答案中添加更多细节,但谢天谢地,他在Tallmaris 的回答中留下的评论解决了这一点。Jon Skeet 在他的回答中进一步扩展了原因,但归结为“oppDocResult 是使用延迟执行的 LINQ 查询的结果”。

于 2012-05-15T16:34:06.603 回答
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>.

于 2012-05-15T15:54:34.733 回答
1

像这样修复,转换为 List 而不是保留 IEnumerable:

var oppDocResult = docnames
        .Where(docName => !String.IsNullOrEmpty(docName))
        .Select(docName
            => new OppDocumentServiceResult
            {
                Document = docName
            }).ToList();

我只能猜测(这真的是在黑暗中拍摄!)这背后的原因是在 IEnumerable 中的元素就像真实元素的“代理”?基本上,由 Linq 查询定义的 Enumerable 就像是获取所有数据的“承诺”,所以每次迭代都会取回原始项目?这并不能解释为什么正常的属性仍然存在......

所以,修复就在那里,但我担心的解释不是……至少不是来自我:(

于 2012-05-15T16:26:23.710 回答
0

只需在课堂上更改代码

public List<string> Reasons { get; private set; }
于 2012-05-15T16:02:47.150 回答