我在C# 中有以下设计问题编程应用程序。
我有类A和B都派生自 C。我无法更改他们的定义,因为它们是在外部程序集中定义的,也没有定义为部分的。
我想要实现的是根据天气来区分功能,前提是C 对象是A 或 B类型。当然,我不想使用 if 语句来比较所提供对象的运行时类型。扩展方法可以吗?我不这么认为。有什么解决办法吗?:)
我在C# 中有以下设计问题编程应用程序。
我有类A和B都派生自 C。我无法更改他们的定义,因为它们是在外部程序集中定义的,也没有定义为部分的。
我想要实现的是根据天气来区分功能,前提是C 对象是A 或 B类型。当然,我不想使用 if 语句来比较所提供对象的运行时类型。扩展方法可以吗?我不这么认为。有什么解决办法吗?:)
通过使用泛型应该可以使用扩展方法。有多种方法是可能的,但这是最简单的一种。虽然你确实明白了,如果
public static void Foo<T>(this T objectC)
where T: C
{
if(typeof(T)==typeof(B){ //or for runtime check: if(objectC is B)
//specific
}
}
然后,您可以在 A 或 B 的任何实例上调用 Foo。
您提到您不想要 if 语句,但我不确定您要避免这种情况的范围?完全避免它的唯一方法是拥有 2 种扩展方法,一种用于 A,一种用于 B,(反过来可以调用 C 的通用方法),但我认为您试图避免使用多种扩展方法?
编辑如果您绝对想阻止 if,您将不得不使用多种扩展方法,如 Frederik 的帖子中所示。您也可以为基类添加一个扩展,只有在编译期间类型未知时才会调用它。但这仍然需要一个 if ;)
public static void Foo(this A a)
{
}
public static void Foo(this B b)
{
}
public static void Foo(this C c)
{
if(c is A)
Foo((A)c);
else if(c is B)
Foo((B)c);
else
throw new NotSupportedException(c.GetType().FullName);
}
如果在编译时类型总是已知的,您可以简单地使用 A 和 B 的 2 种扩展方法。
你可以试试这个:
public static class CExtensions {
public static void DoIt( this B foo ) {}
public static void DoIt( this A foo ) {}
}
但是,我认为这不会起作用:
C x = new A();
x.DoIt();
我认为它不会编译,但是,我现在无法测试它。
纯粹从可行性的角度来看,使用反射和扩展方法是可能的。它在你的应用程序的上下文中是否有意义,是你应该判断的。解决方案来了....
class C
{
}
class A : C
{
}
class B : C
{
}
static class Extender
{
public static void M(this B b)
{
Console.WriteLine(" Extension method on B");
}
public static void M(this A a)
{
Console.WriteLine(" Extension method on A");
}
}
static void Main(string[] args)
{
C c = new A();// The actual instance here will be created using some factory.
object instance = Activator.CreateInstance(c.GetType());
Type typeToFind = c.GetType();
Type typeToQuery = typeof(Extender);
var query = from method in typeToQuery.GetMethods(BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic)
where method.IsDefined(typeof(ExtensionAttribute), false)
where method.GetParameters()[0].ParameterType == typeToFind
select method;
// You would be invoking the method based on its name. This is just a quick demo.
foreach (MethodInfo m in query)
{
m.Invoke(instance, new object[] { instance });
}
}
对我来说,这感觉像是设计上的缺陷。如果您不能在不知道运行时类型的情况下覆盖和更改功能的两个子类的公共类,那么您显然对整体设计有一些问题。
那些因运行时类型而异的功能真的应该存在于这些类中吗?
你可以这样做。使用您需要的不同方法创建一个类。在同一个类中定义一个带有此方法签名的委托,并保留一个字典,其中键是类型,值是委托。最后,您可以为您的类 C 创建一个扩展方法,该方法使用正在执行方法本身并执行正确方法的对象的类型来查找字典。像这样的东西:
public static class CExtender
{
private static void DoItA(C anA)
{
MessageBox.Show("A");
}
private static void DoItB(C aB)
{
MessageBox.Show("B");
}
private static void DoItC(C aC)
{
MessageBox.Show("C");
}
delegate void DoItDel(C aC);
private static Dictionary<Type, DoItDel> _doItDels;
private static Dictionary<Type, DoItDel> DoItDels
{
get
{
if (_doItDels == null)
{
_doItDels = new Dictionary<Type, DoItDel>();
_doItDels[typeof(A)] = new DoItDel(DoItA);
_doItDels[typeof(B)] = new DoItDel(DoItB);
}
return _doItDels;
}
}
// the only public part is the extension method
public static void DoIt(this C aC)
{
DoItDel aDel;
if (DoItDels.TryGetValue(aC.GetType(), out aDel))
aDel(aC);
else
DoItC(aC);
}
}
在这种情况下,您可以使用装饰器模式。
即,您可以拥有自己的 2 个新类,比如说 A1、B1 都从 C 派生。(前提是 C 不是密封类。如果是这种情况,解决方案会有点不同)。A1 和 B1 也需要相应地包装 A 和 B。您可以通过 A1 和 B1 的构造函数注入 A 和 B 的实例。
您还可以拥有一个 A1 和 B1 都实现的通用接口。接口需要有你需要 A1 和 B1 以自己的方式实现的方法。(A1 和 B1 可以根据需要将调用委托给 A 或 B)
这样,在您的客户端代码中,您可以通过接口类型引用 A1 和 B1 实例,并执行接口中定义的常见操作,而无需知道它们的实际具体实现是什么。
如果我不清楚,请告诉我您是否需要代码示例。
好的,我找到了答案。动态关键字是这里的线索。我们可以写:
void Handle(C c)
{
dynamic cc = c;
HandleSpecific(cc);
}
void HandleSpecific(A a)
{
//Specific behavior A
}
void HandleSpecific(B b)
{
//Specific behavior B
}
缺点当然是 - 由于此处引入的运行时绑定和轻微的性能影响而导致异常风险。