Why do I (sometimes) have to reference assemblies

2019-01-15 07:39发布

问题:

I have an assembly A that defines an interface with some overloads:

public interface ITransform
{
    Point InverseTransform(Point point);
    Rect InverseTransform(Rect value);
    System.Drawing.Point InverseTransform(System.Drawing.Point point);
}

...and an assembly B that references A (the binary, not the project) and calls one of the overloads:

var transform =
    (other.Source.TransformToDisplay != null &&
    other.Source.TransformToDisplay.Valid) ?
    other.Source.TransformToDisplay : null;
if (transform != null)
{
    e.Location = transform.InverseTransform(e.Location);
}

To be precise, it calls the System.Windows.Point overload of the InverseTransform method, because that is the type of the property Location in e.

But when I build B in the IDE I get:

error CS0012: The type 'System.Drawing.Point' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.

even though that's not even the overload I am calling. When I comment out the line where the overloaded method InverseTransform is called, it builds fine even though I'm still instantiating an object of type ITransform.

Why? And is there a way to fix this without having to add a reference to System.Drawing everywhere?

回答1:

The compiler needs to know what a System.Drawing.Point is in order to prove that it's not the correct overload (eg, if it has an implicit conversion).



回答2:

That method makes use of something defined in System.Drawing. If you uncomment it then that assembly no longer will by trying to use System.Drawing; hence, no requirement.

Think of it this way, when you go off to perform your action .NET says ok I'm making a call to this guy defined in this assembly and looks for the appropriate code to execute. It can't find it so it throws up it's hands and says I give up you tell me where it is.

Just make it a habit of referencing every DLL you might potentially use.



回答3:

namespace ClassLibrary1
{
   public interface ITransform
   {
      dynamic InverseTransform(dynamic point);
   }
}

using ClassLibrary1;
using Moq;
namespace ConsoleApplication9
{
   interface IPoint { }
   class Point : IPoint { }

   class Program
   {
      static void Main(string[] args)
      {
         var transform = new Mock<ITransform>();
         IPoint x = transform.Object.InverseTransform(new Point());
      }
   }
}

Instead of telling you what you can't do...

A way to fix this would entail introducing IPoint Transform(IPoint x) as the only method in your interface, together with IPoint interface. This would mean that System.Drawing would have to comply to your IPoint as well.

If you want that level of decoupling, dynamic keyword comes to mind, since you can't get Drawing.Point to implement an interface after-the-fact. Just be sure to have really great unit test coverage on this part of code, and expect it to perform somewhat slower.

This way, you'd only have to reference System.Drawing only in assemblies where you're actually using it.

EDIT Reflector says that the signature of System.Drawing.Point is

[Serializable, StructLayout(LayoutKind.Sequential), TypeConverter(typeof(PointConverter)), ComVisible(true)]
public struct Point { }


回答4:

The only difference between overloads are the types. That is why the compiler cannot distinguish which overload you're using without looking at the type.

Since the type is not referenced by the executing assembly, the compiler doesn't know the type and needs a direct reference to the assembly containing the type definition.

I ran in this issue myself and didn't want to add a direct reference to the assembly containing the type. I simply added an argument (boolean) to one of the methods so they are no longer overloads of eachother. The compiler then understood the difference between the methods, even if they have the same name, because they have a different amount of arguments. It no longer needed a reference to the assembly containing the type. I know it's not an ideal solution, but I couldn't find another solution as my method was a constructor so I couldn't change its signature in any other way.



回答5:

To resolve this (and providing you don't have too much calls to wrap etc.)
you could simply define an extension wrapper for that 'Point' call you're only using e.g.

public static Point MyInverseTransform(this ITransform mytransform, Point point)
{
    return mytransform.InverseTransform(point);
}

...feed that lib (where the extension is) the System.Drawing reference
(and to avoid having to add your 'wrapper lib' everywhere as that would defeat the purpose, just put it in some common lib which you have referenced already, related to the problem. Best is if part of the 'source' lib but saying in case you cannot change that)...

and call it then via MyInverseTransform instead.