Why does calling IEnumerable<string>.Count() create an additional assembly dependency ?
- by Gishu
Assume this chain of dll references
Tests.dll >> Automation.dll >> White.Core.dll
with the following line of code in Tests.dll, where everything builds
result.MissingPaths
Now when I change this to
result.MissingPaths.Count()
I get the following build error for Tests.dll "White.UIItem is not defined in an assembly that is not referenced. You must add a reference to White.Core.dll." And I don't want to do that because it breaks my layering.
Here is the type definition for result, which is in Automation.dll
public class HasResult
{
public HasResult(IEnumerable<string> missingPaths )
{ MissingPaths = missingPaths; }
public IEnumerable<string> MissingPaths { get; set; }
public bool AllExist
{
get { return !MissingPaths.Any(); }
}
}
Down the call chain the input param to this ctor is created via (The TreeNode class is in White.Core.dll)
assetPaths.Where(assetPath => !FindTreeNodeUsingCache(treeHandle, assetPath));
Why does this dependency leak when calling Count() on IEnumerable ? I then suspected that lazy evaluation was causing this (for some reason) - so I slotted in an ToArray() in the above line but didn't work.
Update 2011 01 07: Curiouser and Curiouser!
it won't build until I add a White.Core reference. So I add a reference and build it (in order to find the elusive dependency source). Open it up in Reflector and the only references listed are Automation, mscorlib, System.core and NUnit. So the compiler threw away the White reference as it was not needed. ILDASM also confirms that there is no White AssemblyRef entry.
Any ideas on how to get to the bottom of this thing (primarily for 'now I wanna know why' reasons)? What are the chances that this is an VS2010/MSBuild bug?
Update 2011 01 07 #2
As per Shimmy's suggestion, tried calling the method explcitly as an extension method
Enumerable.Count(result.MissingPaths)
and it stops cribbing (not sure why).
However I moved some code around after that and now I'm getting the same issue at a different location using IEnumerable - this time reading and filtering lines out of a file on disk (totally unrelated to White). Seems like it's a 'symptom-fix'.
var lines = File.ReadLines(aFilePath).ToArray();
once again, if I remove the ToArray() it compiles again - it seems that any method that causes the enumerable to be evaluated (ToArray, Count, ToList, etc.) causes this. Let me try and get a working tiny-app to demo this issue...
Update 2011 01 07 #3
Phew! More information.. It turns out the problem is just in one source file - this file is LINQ-phobic. Any call to an Enumerable extension method has to be explicitly called out.
The refactorings that I did caused a new method to be moved into this source file, which had some LINQ :) Still no clue as to why this class dislikes 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)));