为什么调用IEnumerable<string>.Count()会创建额外的程序集依赖?

8
假设这是一条 DLL 引用链。
Tests.dll >> Automation.dll >> White.Core.dll

在Tests.dll中,使用以下代码行,其中一切都构建完成。
result.MissingPaths 

现在当我将此更改为

result.MissingPaths.Count()

我对 Tests.dll 进行构建时出现以下错误:“White.UIItem 未在一个未被引用的程序集中定义。您必须添加对 White.Core.dll 的引用。”但我不想这样做,因为它会破坏我的分层结构。
下面是 Automation.dll 中 result 的类型定义。
public class HasResult
        {
            public HasResult(IEnumerable<string> missingPaths )
            {   MissingPaths = missingPaths;           }

            public IEnumerable<string> MissingPaths { get; set; }

            public bool AllExist
            {
                get { return !MissingPaths.Any(); }
            }
        }

在调用链中,此构造函数的输入参数是通过以下方式创建的(TreeNode类位于White.Core.dll中)

assetPaths.Where(assetPath => !FindTreeNodeUsingCache(treeHandle, assetPath));

为什么在IEnumerable上调用Count()时会导致依赖项泄漏?我怀疑是惰性求值引起的(出于某种原因)- 因此,我在上面的代码行中插入了一个ToArray(),但没有起作用。
更新2011年01月07日:更加好奇了! 如果不添加White.Core引用,则无法构建。 因此,我添加了一个引用并构建它(为了找到难以捉摸的依赖项来源)。 在Reflector中打开它,列出的唯一参考文献是Automation,mscorlib,System.core和NUnit。 因此,编译器放弃了White引用,因为它不需要。 ILDASM还确认没有White AssemblyRef条目。
有什么方法可以彻底解决这个问题吗(主要是出于“现在我想知道为什么”的原因)? 这是VS2010 / MSBuild的错误的可能性有多大?
更新2011年01月07日#2 根据Shimmy的建议,尝试将该方法显式地调用为扩展方法。
Enumerable.Count(result.MissingPaths)

它停止了抱怨(不确定为什么)。但是在此之后,我移动了一些代码,现在在使用IEnumerable时在不同的位置遇到了相同的问题 - 这次是从磁盘文件中读取和过滤行(与White完全无关)。似乎这是一个“症状修复”。

var lines = File.ReadLines(aFilePath).ToArray();

再次说明,如果我删除ToArray(),它就可以再次编译 - 似乎任何导致可枚举对象被评估的方法(ToArray、Count、ToList等)都会导致此问题。让我试着创建一个可演示此问题的小应用程序...

更新 2011 01 07 #3 哦!更多信息... 原来问题只出现在一个源文件中 - 这个文件害怕LINQ。必须显式调用对Enumerable扩展方法的任何调用。 我进行的重构导致一个新方法被移动到这个源文件中,其中包含一些LINQ :) 仍然不知道为什么这个类不喜欢LINQ。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using G.S.OurAutomation.Constants;
using G.S.OurAutomation.Framework;
using NUnit.Framework;

namespace G.S.AcceptanceTests
{
    public abstract class ConfigureThingBase : OurTestFixture
    {
      ....
        private static IEnumerable<string> GetExpectedThingsFor(string param)
        {
                // even this won't compile - although it compiles fine in an adjoining source file in the same assembly
                //IEnumerable<string> s = new string[0];
                //Console.WriteLine(s.Count()); 



                // this is the line that is now causing a build failure   
            //  var expectedInfo = File.ReadLines(someCsvFilePath))
//                  .Where(line => !line.StartsWith("REM", StringComparison.InvariantCultureIgnoreCase))
//                  .Select(line => line.Replace("%PLACEHOLDER%", param))
//                  .ToArray();

                // Unrolling the LINQ above removes the build error

            var expectedInfo =
                Enumerable.ToArray(
                    Enumerable.Select(
                        Enumerable.Where(
                            File.ReadLines(someCsvFilePath)),
                            line => !line.StartsWith("REM", StringComparison.InvariantCultureIgnoreCase)),
                        line => line.Replace("%PLACEHOLDER%", param)));

更新 2011年01月11日 #4 缩小了嫌疑人范围,但找不到动机 :)
周末后继续任务..并使用排除法,最终锁定了有问题的部分。 问题出在Tests.dll源文件中的以下使用指令。

using G.S.OurAutomation.Framework;

接下来,我把矛头对准了这个命名空间中最可能的嫌疑人,并且将WhiteExtensions置于焦点之下。

namespace G.S.OurAutomation.Framework
{
   public static class WhiteExtensions
   {
        public static T PollAndGet<T>(this Window parentWindow, string automationId) where T : UIItem ...
        public static Window WaitForWindowWithTitle(this Application application, string windowTitle) ...
        public static bool HasTreeNode(this Tree treeHandle, string assetPath) ...
        public static HasTreeNodesResult HasTreeNodes(this Tree treeHandle, IEnumerable<string> assetPaths)...
   }
}

这导致了3个修复方案,都可以解决问题。
  1. 将扩展方法转换为普通静态方法。
  2. 将此类移动到子命名空间G.S.OurAutomation.Framework.White
  3. 将其设置为内部类(因为它是供内部使用的..再次选择最严格的访问修饰符的指南使我受挫了。)
尽管我的特定实例已经被修复,但这个更新能否帮助有人解释这个原因呢?如果不能,那么Shimmy会得到勾选:),因为他指向了正确的方向。

2
你能提供更多的代码吗?也许将DLL重命名为更有意义的名称?也许在声明扩展方法时没有引用W.Type? - James
尝试将 W.dll 添加为 T 项目的引用。 - Steven
@James - 给程序集命名 :) 代码的简要说明是:检查GUI上显示的树中是否存在某些节点?结果是一个类型,它包装了一个布尔成功值+一个缺失节点列表(如果有)。因此,Tests.dll使用Automation.dll - Driver.HasNodes()来完成其工作,但是Automation.dll使用White来完成工作的实现细节不应该从Automation层中泄露出来。代码涉及从内部层向客户端返回IEnumerable<string>(完整路径Root\parent\child)以进行错误报告。 - Gishu
这可能会听起来很奇怪,但请尝试注释掉 AllExist 的获取代码并替换为一些通用的东西(如return true;return false;)。我只是想看看是否消除那里的LINQ(扩展方法)可以有所帮助(尽管这可能不会有任何帮助,但我对此一无所知)。 - James
如果只是那个文件的问题... 把里面的所有东西都移出来 :P 虽然可能是你的某个using语句与其他东西冲突了或者... ??? - James
显示剩余5条评论
3个回答

4
尝试调用System.Linq.Enumerable.Count(result.MissingPaths)(可以带有完整的命名空间引用或不带)。 Count是扩展方法,您可以显式调用它。
在Gishu的Update#2之后更新:
原因相同,函数ToArrayCountToList等都是在System.Linq.Enumerable中声明的扩展方法。
顺便说一下,重要提示:您确认文件导入了命名空间System.Linq(using System.Linq)吗?这可能甚至可以解决所有问题。

这个可行,但我不知道为什么!我太高兴了。几次重构之后...我在另一个地方遇到了同样的错误,我再次使用IEnumerable<string> - 这次是为了读取文件中的行并过滤一些不需要的行。您有特别的原因建议这样做吗? - Gishu
1
因为IEnumerable<T>不包含Count函数,System.Linq命名空间中的某些内容包含它(这被称为扩展方法)。http://msdn.microsoft.com/en-us/library/bb383977.aspx - Jeff Hubbard
@shimmy - 是的,我知道它们是扩展方法;我的意思是我仍然看不出调用collection.Select(..Enumerable.Select(collection,..之间的区别,除非collection有一个实例方法Select,它会覆盖扩展方法。还有其他原因会导致这种解析失败吗? - Gishu
你的当前文件是否导入了 System.Linq 命名空间? - Shimmy Weitzhandler

1
编译器可能需要查看White.Core.dll以确定您的类是否定义了Count()方法。由于没有,它使用了IEnumerable<>扩展方法的Count,因此实际上并不需要White.Core.dll,但是如果不检查它,就无法知道这一点。
但我无法解释为什么对于IEnumerable<string>而言会出现这种情况,除非编译器正在检查您是否在使用协变?

你是指在哪个类上定义了Count()方法?是HasResult类吗? - Gishu

0

改成怎样?

public HasResult(IEnumerable<string> missingPaths )
{   MissingPaths = missingPaths;           }

public HasResult(IEnumerable<string> missingPaths )
{   MissingPaths = missingPaths.ToArray();           }

应该在所有使用结果的情况下消除懒加载场景。


尝试过了 - 没有像我在帖子末尾提到的那样工作。 - Gishu
@Gishu,以防万一,建议您检查一下结果构造的位置是否有遗漏。另一个可能性是A.dll中其他地方使用了MissingPaths setter - 您能将其设置为私有吗?此外,编译器抱怨的W类型是什么? - VinayC
它正在要求一个在White.dll中定义的UIItem类型 - 但是我一直保持Tests.dll不引用White库(还进行了查找替换以进行检查)。我也试过在调用堆栈上不同位置强制进行评估,但并没有起作用。 - Gishu
@Gishu,Hightechrider的解释(以及Shimmy的解决方案)是有道理的——唯一让我困惑的是编译器试图调查IEnumerable<string>UIItem之间的关系! - VinayC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接