12

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?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
mtijn
  • 3,610
  • 2
  • 33
  • 55
  • Out of curiosity, could you rename the last overload to `InverseTransform2` and try again? I don't know the answer, but I'm wondering if it has anything to do with overload resolution. – Sergey Kalinichenko Apr 18 '12 at 13:51
  • Is `e.Location` specifically a `System.Windows.Point` object, or another class that derives from `System.Windows.Point`? – Justin Morgan - On strike Apr 18 '12 at 14:08
  • @dasblinkenlight: yes it has to do with overload resolution, using different method names solves it but I don't want to change the interface – mtijn Apr 18 '12 at 14:27
  • @Justin Morgan: e.Location is a System.Windows.Point and even if it derived from that it's always a System.Windows.Point and never a System.Drawing.Point – mtijn Apr 18 '12 at 14:28
  • It may matter to the compiler; see my comment on Slaks's answer. Since the compiler doesn't know what a `System.Drawing.Point` is, it doesn't know which overload is the most specific to `e.Location`. Suppose `System.Drawing.Point` is an interface, and `e.Location` is a derived class that implements that interface? That would make the second overload the correct one to use. Does the compiler know that `e.Location` will always be a `System.Windows.Point`? – Justin Morgan - On strike Apr 18 '12 at 14:36

5 Answers5

13

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

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • but even if I specifically (re)cast to System.Windows.Point it still doesn't compile. why prove that it *isn't* a System.Drawing.Point when it clearly *is* a System.Windows.Point? – mtijn Apr 18 '12 at 13:59
  • @mtijn: You have defined the interface that way: It expects a System.Drawing.Point: `System.Drawing.Point InverseTransform(System.Drawing.Point point);` Even if you have a cast, it still needs to know what a System.Drawing.Point is. – Skalli Apr 18 '12 at 14:02
  • Do you think this is a situation where the compiler could be smarter? If `e.Location` is known to be a `System.Windows.Point` (and not a derivation from that), then there's no way `System.Drawing.Point` could be the most specific overload. It could conceivably skip that part of the overload resolution. – Justin Morgan - On strike Apr 18 '12 at 14:13
  • +1 this helped me too. By changing the overloaded method name (to something with an overload that didn't reference the missing type) the project compiled as expected – Matt Klein Jan 04 '13 at 21:11
4

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.

scottheckel
  • 9,106
  • 1
  • 35
  • 47
2
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 { }
GregC
  • 7,737
  • 2
  • 53
  • 67
  • didn't think of using dynamic yet.. I tried 'dynamic transform = ...' instead of 'var transform = ...' and that seems to compile but I haven't run it yet. but if I'd be changing the interface anyway I could have achieved the same effect with generics, right? – mtijn Apr 18 '12 at 14:32
  • If perf is your concern, try using FastMember. If you feel you'd need compiler support, see the answer by SLaks. – GregC Apr 18 '12 at 14:58
  • 1
    I agree that it would be good if compiler didn't ask you to add a reference unless the type will have to be compiled into the IL. This would reduce noise at the expense of making compile process a little more implicit. – GregC Apr 18 '12 at 15:02
  • I like the solution and the use of dynamic in this case is brilliant. even though I'm not fond of using dynamic at least this is 1 example of its usefulness, but personally I would have used dynamic for the transform variable rather than the interface because the impact is much smaller. however I'm not accepting this as an answer because it doesn't answer the title of the post and I eventually just used references. – mtijn Apr 20 '12 at 09:35
  • @mtijn I am a big proponent of strongly-typed programming, but it's important to know that other options are available. I am glad that you found my answer engaging in this discussion. – GregC Apr 20 '12 at 15:46
2

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.

Sam Debruyn
  • 928
  • 1
  • 8
  • 22
0

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.

NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51