C#中替代"using"指令关键字的选择?

5
我刚刚看完了Bob Martin在NDC的一个剧集,他说C#页面顶部的“using”指令很糟糕,因为它们创建/暗示组件之间的紧密耦合。
有哪些方法可以使用外部.dll文件而不添加项目引用和using语句?
我记得V6曾经让你通过ProgId字符串创建对象——我不确定这是否是我要寻找的技术,但这是一个不需要项目引用就可以使用dll的语言的例子。
编辑:这里是会议链接。对不起,我没有确切的引文或演示中的分钟数,我只能凭记忆回答。

1
他真的这么说了吗,还是你理解错了他的话? - Vinko Vrsalovic
3
提供一个链接会很有帮助。我想听听他真正说了什么。 - tvanfosson
6
为避免混淆,Microsoft将其称为使用“指示符”。使用语句(关键字)通常是指在方法内部使用的用于自动调用Dispose释放资源的语句。 - Ash
5个回答

7
我相信Bob Martin实际上是在提到早期绑定和晚期绑定。
在.NET中,通过反射(reflection),特别是Activator类,可以使用文件名或程序集名称创建外部程序集的类型,从而实现晚期绑定。
通常,using指令(不是using语句)与直接引用外部程序集并行。也就是说,您添加对程序集的引用,然后添加using指令,以避免在使用外部类型时需要键入完整的命名空间层次结构。
因此,如果您发现代码顶部有大量的using指令,则可能会直接引用许多其他类型,并增加您的代码对这些类型的耦合/依赖性。
我猜这就是Bob称它们为不好的原因。对于问题“这真的不好吗?”的答案非常主观且具体情况而定。
总的来说,在设计软件时,解耦组件几乎总是一个很好的目标。这是因为它允许您在对系统的其余部分影响最小的情况下更改系统的某些部分。根据我读过的一两本Bob Martins书籍,我认为这就是他的意思。

6

using 只是命名空间的一种快捷方式,它们并不是对外部文件的引用。因此,这并没有什么意义。

无论如何,你可以创建一个接口DLL(只有接口的DLL),以便动态加载和使用不同的程序集,并创建类型(通过反射),你可以将其强制转换为众所周知的接口。这是在保持强类型语言和早期绑定的好处的同时放松外部引用的正确方法。

查看 AssemblyAppDomain 类来加载程序集,还有 Activator 来按名称创建类型实例。


6
同意。这个说“using”语句不好的人显然不知道他在说什么。 - Noldorin
4
@Noldorin: Robert C. Martin不知道自己在说什么?! - Mark Seemann
1
或者他被误解了。谁对谁错并不重要,因为“收到的信息”没有意义。 - Lucero

6

使用using语句本身并不是坏事——问题在于你使用太多的using语句。

例如using System;这样的语句本身很少会有问题,但是如果在同一个代码文件中使用了很多(我会说超过3-6个,具体取决于哪些),这可能是紧密耦合的指示

您可以同样地将类似的经验法则应用于项目本身的引用数量。

解决紧密耦合的方法是面向接口编程和依赖注入(DI)。

从VB中记住的ProgId方式只是COM在运行。实质上,您使用该ProgId获取实现所需接口的实例的引用。缺点是仅当COM对象被普遍注册时才起作用。还记得dll地狱吗?

您仍然可以使用某些DI的特定风格来应用相同的原则,只是现在接口是.NET类型而不是在IDL中定义的,并且您需要某种DI容器来提供具体实现。


1
我会写成 "这可能是紧耦合的一个迹象" 而不是 "如果是紧耦合的一个迹象"。 - Vinko Vrsalovic
@Vinko Vrsalovic:不,我不同意:如果你有二十个using语句,那肯定是紧耦合的迹象 - Mark Seemann
那么就这样说吧:“这是紧耦合的一个指示”。 - Vinko Vrsalovic
@Vinko Vrsalovic:哦,但我也想强调术语紧耦合,因为我认为那才是问题的关键所在。 - Mark Seemann

2
您可以使用反射技术:
// Load the assembly
Assembly assembly = Assembly.LoadFrom(@"c:\path\Tools.dll");
// Select a type
Type type = assembly.GetType("Tools.Utility");
// invoke a method on this type
type.InvokeMember("SomeMethod", BindingFlags.Static, null, null, new object[0]);

4
这种调用方式被称为“晚期绑定”,它不仅速度较慢,容易出错,而且在像 C# 这样不支持开箱即用的晚期绑定的语言中(例如通过编译器魔法实现),这种方式还非常繁琐。如果可能的话,我会避免使用这种方法。 - Lucero
最好使用依赖注入框架来处理这种事情,说实话。对于新手来说更好。 - user1228

1

你可以通过反射来实现你所说的功能。你可以在运行时加载程序集,通过反射获取类等信息并动态调用它们。

个人而言,我不会这样做以避免耦合。对我来说,这是反射的一个糟糕使用方式,除非有特殊原因不这样做,否则我更愿意将其添加到项目中并引用它。反射会增加系统开销,并且你无法获得编译时的安全性优势。


此外,这种“技巧”并不能避免耦合。你的代码对于外部 DLL 的耦合程度并没有因为动态调用而降低——如果 DLL 不存在,你的代码将无法工作。 - MusiGenesis
1
如果您拥有一个接口程序集并动态加载具有接口的对象,则可以减少耦合。依赖注入也是一种不错的方法。 - kenny
@Kenny:在那种情况下,你是完全正确的。我更多地是在指出,如果你只是删除一个引用,而是动态加载它,你并没有改善情况。 - MusiGenesis

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