为什么我有时候需要引用被我引用的程序集所引用的程序集?

12

我有一个定义了一些重载的接口的汇编A:

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

...还有一个汇编B引用了A(二进制文件,不是项目),并调用其中的一个重载版本:

var transform =
    (other.Source.TransformToDisplay != null &&
    other.Source.TransformToDisplay.Valid) ?
    other.Source.TransformToDisplay : null;
if (transform != null)
{
    e.Location = transform.InverseTransform(e.Location);
}
更准确地说,它调用了InverseTransform方法的 System.Windows.Point 重载,因为e中的Location属性是该类型。但是,当我在IDE中构建B时,会出现以下错误:

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

尽管这甚至不是我正在调用的重载。当我注释掉调用重载方法InverseTransform的行时,它能够正常构建,即使我仍然实例化了一个ITransform类型的对象。为什么?有没有一种方法可以修复此问题而无需在各处添加对System.Drawing的引用?

出于好奇,你能把最后一个重载改名为 InverseTransform2 然后再试一下吗?我不知道答案,但我想知道它是否与重载决议有关。 - Sergey Kalinichenko
e.Location 是一个 System.Windows.Point 对象,还是从 System.Windows.Point 派生的另一个类? - Justin Morgan
@dasblinkenlight:是的,这与重载解析有关,使用不同的方法名称可以解决问题,但我不想更改接口。 - mtijn
@Justin Morgan:e.Location 是一个 System.Windows.Point,即使它是从那个类派生而来的,它仍然是一个 System.Windows.Point,而不是一个 System.Drawing.Point。 - mtijn
编译器可能会关注此问题,可以查看我在Slaks的答案中的评论。由于编译器不知道 System.Drawing.Point 是什么,它也不知道哪个重载是最特定于 e.Location 的。假设 System.Drawing.Point 是一个接口,并且 e.Location 是实现该接口的派生类?那么第二个重载就是正确的选择。编译器是否知道 e.Location 总是一个 System.Windows.Point - Justin Morgan
5个回答

13
编译器需要知道什么是System.Drawing.Point,以便证明它不是正确的重载(例如,如果它具有隐式转换)。

但即使我明确地将其重新转换为System.Windows.Point,它仍无法编译。为什么要证明它不是System.Drawing.Point,当它显然是System.Windows.Point? - mtijn
@mtijn:你已经这样定义了接口:它期望一个System.Drawing.Point:System.Drawing.Point InverseTransform(System.Drawing.Point point); 即使你有一个强制转换,它仍然需要知道System.Drawing.Point是什么。 - Skalli
你认为这是编译器可以更加智能化的情况吗?如果 e.Location 已知为 System.Windows.Point(而不是继承自它),那么 System.Drawing.Point 不可能是最特定的重载方式。编译器可以考虑跳过这部分重载决议。 - Justin Morgan
+1 这也帮助了我。通过更改重载方法的名称(使用没有引用缺失类型的重载名称),项目按预期编译。 - Matt Klein

4
那种方法利用了System.Drawing中定义的某些内容。如果你取消注释,那么该程序集将不再尝试使用System.Drawing,因此没有要求。
这样想,当你执行操作时,.NET会说好的,我正在调用这个在这个程序集中定义的人,并寻找适当的代码来执行。它找不到,所以它举起手来说我放弃了,你告诉我在哪里。
只需养成引用可能使用的每个DLL的习惯即可。

不,它调用的是该方法的System.Windows.Point重载而不是System.Drawing.Point。 - mtijn
1
就像@SLaks所说的那样,你仍在使用它。 - scottheckel

2
唯一的重载差异在于类型。这就是为什么编译器无法区分使用哪个重载而需要查看类型。
由于类型不被执行程序引用,编译器不知道类型并需要直接引用包含类型定义的程序集。
我自己遇到了这个问题,不想添加对包含类型的程序集的直接引用。我只是向其中一个方法添加了一个参数(布尔值),以便它们不再相互重载。即使它们具有相同的名称,编译器也能理解方法之间的差异,因为它们具有不同数量的参数。它不再需要引用包含类型定义的程序集。我知道这不是一个理想的解决方案,但由于我的方法是构造函数,所以我无法以其他方式更改其签名。

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

与其告诉你什么不能做...

解决这个问题的方法是引入 IPoint Transform(IPoint x) 作为您接口中唯一的方法,以及 IPoint 接口。这意味着 System.Drawing 也必须遵守您的 IPoint。

如果您想要达到这种程度的解耦,那么动态关键字就会在脑海中浮现,因为您无法让 Drawing.Point 在事后实现接口。只需确保对代码的这部分进行非常好的单元测试覆盖,并且预计它的性能会略有降低。

这样,您只需要在实际使用它的程序集中引用 System.Drawing。

编辑 Reflector 表明 System.Drawing.Point 的签名为

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

我之前没有想到使用动态(dynamic)。我尝试了使用'dynamic transform = ...'代替'var transform = ...',这似乎可以编译通过,但我还没有运行它。但是如果我无论如何都要更改接口,我可以通过泛型实现相同的效果,对吗? - mtijn
如果性能是您关注的重点,请尝试使用FastMember。如果您觉得需要编译器支持,请参见SLaks的回答。 - GregC
1
我认为如果编译器不要求你添加引用,除非该类型必须编译到IL中,那将是很好的。这将减少噪音,但会使编译过程更加隐式。 - GregC
我喜欢这个解决方案,使用动态语言在这种情况下是非常聪明的。虽然我不喜欢使用动态语言,但至少这是它有用性的一个例子,但是个人而言,我会将动态语言用于转换变量而不是接口,因为影响要小得多。但是,我不会将其作为答案接受,因为它没有回答帖子的标题,最终我只是使用了参考资料。 - mtijn
@mtijn,我非常支持强类型编程,但重要的是要知道其他选择也是可行的。我很高兴你对我的回答在这个讨论中产生了共鸣。 - GregC

0

为了解决这个问题(并且只要您没有太多需要包装的调用等),您可以简单地为您仅使用的“Point”调用定义一个扩展包装器。

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

...将System.Drawing引用传递给该库(扩展名所在的位置)
(为避免必须在每个地方添加您的“包装器库”而失去目的,只需将其放在一些常用库中,您已经引用了与问题相关的内容。最好是如果部分“源”库但在不能更改它的情况下说)...

然后通过MyInverseTransform调用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接