简体   繁体   中英

Mixing generic methods and extension methods

I created the Class1.GetChild<T>() where T : DependencyObject extension method in lib1.dll assembly. After that, all assemblies that depends on lib1.dll failed to compile with error:

The type 'System.Windows.DependencyObject' is defined in an assemebly that is not referenced. You must add a reference to assembly 'WindowsBase' etc...

Why dependent assemblies requires WindowsBase even if they don't use GetChild ?

.

To reproduce (vs2010 .net4):

lib1.dll (references WindowsBase )

namespace lib1
{
    public static class Class1
    {
        public static T GetChild<T>(this DependencyObject src) where T : DependencyObject
        {
            return default(T);
        }
    }

    public static class Class2
    {
        public static int SomeExtMethod(this string src)
        {
            return 0;
        }
    }
}

lib2.dll (references lib1 but not WindowsBase )

using lib1;
class someClass
{
    void someFct()
    {
        "foo".SomeExtMethod(); // error: The type 'System.Windows.DependencyObject'
                // is defined in an assemebly that is not referenced. 
                // You must add a reference to assembly 'WindowsBase' etc..
    }
}

.

Update:

I think there's definitly something when mixing generic methods and extension methods. I tried to demonstrate the issue in the following sample:

// lib0.dll
namespace lib0
{
    public class Class0 { }
}

// lib1.dll
using lib0;
namespace lib1
{
    public static class Class1
    {
        public static void methodA<T>() where T : Class0 { }    // A
        public static void methodB(Class0 e) { }                // B
        public static void methodC(this int src) { }            // C
    }

    public static class Class2
    {
        public static void methodD(this String s) { }
    }
}

// lib2.dll
using lib1;
class someClass
{
    void someFct()
    {
        Class2.methodD("");  // always compile successfully
        "".methodD();        // raise the 'must add reference to lib0' error depending on config. see details below.
    }
}

A, //B, //C -> compile ok

A, B, //C -> compile ok

//A, B, C -> compile ok

A, //B, C -> raise error

A, B, C -> raise error

//A means methodA is commented. As Damien pointed out, type inference might play some role. Still curious to know the ins and outs.

Your situation has been answered by Microsoft here: https://connect.microsoft.com/VisualStudio/feedback/details/668498/problem-with-extension-method-in-c-compiler

There are other use-cases as well independent of extension methods which produce this error wrongly.

Consider this:

  1. Define a generic method in a type, say TP1, defined in library say LB1.
  2. Type constrain the generic method on some type defined in some other library LB2.
  3. Define another method in TP1.
  4. Now in your library reference only LB1 and try to call the second method of type TP1

If you don't use TP1 but some other type defined in LB1, you do not get the error. Also, even if one of the method of type TP1 expects a parameter of the type defined in LB2 (and you do not call this method) it does not produce this error

When one assembly depends on another assembly, the first assembly also depends on all the dependencies of the other--regardless of what is used. Assembly dependencies are effectively decoupled, another version of either assembly can be deployed after compilation, the compiler can't know that under circumstances like this one or more of the dependencies in the second assembly won't be used by the first assembly.

To solve the issue you can simply add a reference to WindowsBase.

Or, as prashanth points out, put the SomeExtMethod into a different assembly so code that uses that doesn't need to take a dependency on WindowsBase.

Update: If you don't use anything from an assembly, you don't need any of its dependencies. But, as soon as you use one assembly, you need all the dependencies of that assembly as well. This is apparent in the way Visual Studio add references. If you add a reference to an assembly, it will copy all the dependent assemblies (not registered in the GAC) into your debug/release directories along with the assembly you added.

Update: As to the compile error: that's the way it was written--there may be no other reason. Is it a good idea to get a compile error if you don't reference dependent assemblies? Maybe, you're likely to use something from a reference and that might use something directly from the references references--better a compile error than a deployment error.

Why not a compile error on every non-referenced secondary dependency? Again, it was written that way. Maybe an error here too would be good; but that would be a breaking change and would require really persuasive reasons.

I'm not sure this can be answered by anyone other than someone on the compiler team. I'm now thinking that it's to do with type inference - but whereas §7.6.5.1 Method Invocations talks about inference, §7.6.5.2 Extension method invocations is silent on the matter - despite the fact that inference obviously does take place when searching for applicable extension methods.

I think it's attempting some form of inference before it's performing the comparison on identifiers (which would immediately rule out the extension method since it's got the wrong name). Obviously, it can't perform any form of inference for this type if it's unable to understand the type constraints.

Hence, when you change your type constraint to just class , it now successfully passes over this method - it can infer a type parameter, but it now eliminates this extension method successfully.

When you reference another assembly, I assume the compiler needs to be able to parse any method signatures defined in that assembly, so it knows where to go to find that function if it sees a call to it.

If you replace your GetChild() function with

    public static T GetChild<T>(this T src)
    {
        if (typeof(T) == typeof(DependencyObject)) return default(T);
        else return default(T);
    }

or something similar to that, it does not require you to include the reference to WindowsBase that you're running into. But if you add where T : DependencyObject to the signature, it does require it.

Effectively, you can use whatever assembly references you want in a project, so long as you don't expose them in any way. Once you expose them, then every other project which uses your library needs to be able to handle them, and thus requires those references themselves.

Maybe ILMerge would solve this problem. The idea is you create 2 dlls and merge them into one. That way you can have a single dll but reference it twice. Then way you can separate the GUI code from other code and only add the reference that you need to the particular project.

The answer is simple. It is because the method is decalre as public. This mean that it is visible to lib2.dll (in your case.) In other word you can call this method.

It also has a constrain that only classes inherited from DependencyObject can call this method. So that is the reason why you need to reference 'WindowsBase'.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM