Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does calling IEnumerable<string>.Count() create an additional assembly dependency?

Tags:

c#

.net

linq

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)));

Update 2011 01 11 #4 Narrowed it down to what seems the perp but no motive :)
Resumed the quest post the weekend.. and using the evergreen process of elimination, was able to zone in on the offending bit. The problem is the following using directive in the source file in Tests.dll

using G.S.OurAutomation.Framework;

Next I went after the most probable suspect within this namespace and I had WhiteExtensions under the spotlight.

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)...
   }
}

This led to 3 fixes, both of which work.

  1. Turn the extension methods into normal static methods.
  2. Move this class into a subnamespace G.S.OurAutomation.Framework.White
  3. Make this an internal class (as it is meant for internal consumption.. once again the guideline of choosing the most restrictive access modifier bites me.)

Although my specific instance is fixed, can this update help someone explain the reason for this ? If not shimmy gets the tick :) for pointing towards the right direction.

like image 684
Gishu Avatar asked Jan 06 '11 09:01

Gishu


People also ask

Does IEnumerable have count?

IEnumerable doesn't have a Count method.

How do I count items in IEnumerable?

IEnumerable has not Count function or property. To get this, you can store count variable (with foreach, for example) or solve using Linq to get count.

What is IEnumerable <> in C#?

IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface. It is the base interface for all non-generic collections that can be enumerated. This works for read-only access to a collection that implements that IEnumerable can be used with a foreach statement.

What does .count do C#?

In its simplest form (without any parameters), the Count() method returns an int indicating the number of elements in the source sequence. IEnumerable<string> strings = new List<string> { "first", "then", "and then", "finally" }; // Will return 4 int result = strings. Count();


2 Answers

Try to call System.Linq.Enumerable.Count(result.MissingPaths) (with or without the full namespace reference).

Count is an extension method, you can call it explicitly.

Update after Gishu's Update#2:

The reason for all is the same, the functions ToArray, Count, ToList etc. are all extension methods declared in System.Linq.Enumerable.

BTW, important: did you double check that the namespace System.Linq is imported (using System.Linq) to your file? that might even solve all your problems.

like image 166
Shimmy Weitzhandler Avatar answered Sep 24 '22 23:09

Shimmy Weitzhandler


The compiler presumably needed to see White.Core.dll to figure out if there was a Count() method defined on your class. Since there wasn't it used the IEnumerable<> extension method's Count and thus didn't need White.Core.dll after all, but it couldn't know that without examining it.

But I can't explain how that could be the case for IEnumerable<string> unless the compiler is checking that you aren't using covariance??

like image 29
Ian Mercer Avatar answered Sep 26 '22 23:09

Ian Mercer