我有一个第三方闭源应用程序,它导出一个COM接口,我通过Interop在我的C#.NET应用程序中使用它。此COM接口导出许多对象,这些对象都显示为System.Object,直到我将它们转换为适当的接口类型。我想分配所有这些对象的属性。因此:
foreach (object x in BigComInterface.Chickens)
{
(x as Chicken).attribute = value;
}
foreach (object x in BigComInterface.Ducks)
{
(x as Duck).attribute = value;
}
但是分配属性很可能(由于特定于应用程序的原因,这是不可避免的)抛出我想要恢复的异常,所以我真的想要一个try / catch。因此:
foreach (object x in BigComInterface.Chickens)
{
try
{
(x as Chicken).attribute = value;
}
catch (Exception ex)
{
// handle...
}
}
foreach (object x in BigComInterface.Ducks)
{
try
{
(x as Duck).attribute = value;
}
catch (Exception ex)
{
// handle...
}
}
显然,这样做会更加清晰:
foreach (object x in BigComInterface.Chickens)
{
SetAttribute<Chicken>(x as Chicken, value);
}
foreach (object x in BigComInterface.Ducks)
{
SetAttribute<Duck>(x as Duck, value);
}
void SetAttribute<T>(T x, System.Object value)
{
try
{
x.attribute = value;
}
catch
{
// handle...
}
}
看到问题?我的 x 值可以是任何类型,因此编译器无法解析 .attribute 。 Chicken和Duck不在任何类型的继承树中,并且它们不共享具有 .attribute 的接口。如果他们这样做,我可以在 T 上为该接口设置约束。但由于课程是闭源的,这对我来说是不可能的。
在我的幻想中,我想要的是一个约束,要求的参数具有.attribute属性,无论它是否实现给定的接口。好的,
void SetAttribute<T>(T x, System.Object value) where T:hasproperty(attribute)
我不知道该怎么做,除了为鸡,鸭,牛,羊等每个切割/粘贴这个小的try / catch块。
我的问题是:当在编译时无法知道实现该属性的接口时,想要在对象上调用特定属性的问题有什么好的解决方法?
答案 0 :(得分:6)
嗯,取决于您的异常处理代码是多么庞大(如果我没有弄错的话,可能就是这样),使用以下技巧可能对您有所帮助:
class Chicken
{
public string attribute { get; set; }
}
class Duck
{
public string attribute { get; set; }
}
interface IHasAttribute
{
string attribute { get; set; }
}
class ChickenWrapper : IHasAttribute
{
private Chicken chick = null;
public string attribute
{
get { return chick.attribute; }
set { chick.attribute = value; }
}
public ChickenWrapper(object chick)
{
this.chick = chick as Chicken;
}
}
class DuckWrapper : IHasAttribute
{
private Duck duck = null;
public string attribute
{
get { return duck.attribute; }
set { duck.attribute = value; }
}
public DuckWrapper(object duck)
{
this.duck = duck as Duck;
}
}
void SetAttribute<T>(T x, string value) where T : IHasAttribute
{
try
{
x.attribute = value;
}
catch
{
// handle...
}
}
答案 1 :(得分:4)
不幸的是,目前这很棘手。在C#4中,动态类型可能对此有所帮助。 COM互操作是动态真正闪耀的地方之一。
然而,与此同时,唯一允许您拥有任何类型对象但没有接口限制的选项将是恢复使用反射。
您可以使用反射来查找“属性”属性,并在运行时设置它的值。
答案 2 :(得分:2)
不幸的是,唯一的方法是使用定义该属性的接口约束type参数,并在所有类型上实现。
由于您没有源,因此无法实现,因此您将不得不使用某种解决方法。 C#是静态类型的,因此不支持你想在这里使用的鸭子类型。即将到来的最好的事情(在C#4中)将是将对象键入为dynamic
并在执行时解析属性调用(请注意,此方法也不是通用的,因为您不能将泛型类型参数约束为{ {1}})。
答案 3 :(得分:2)
为了不违反DRY原则,您可以使用反射。如果你真的想使用泛型,你可以为每种类型的第三方对象使用包装类,并让包装器实现一个可用于约束泛型类型参数的接口。
如何用反射完成它的一个例子。代码未经过测试。
if (string.IsNullOrWhiteSpace(car))
{
如果要加速性能代码,可以按类型 void SetAttribute(object chickenDuckOrWhatever, string attributeName, string value)
{
var typeOfObject = chickenDuckOrWhatever.GetType();
var property = typeOfObject.GetProperty(attributeName);
if (property != null)
{
property.SetValue(chickenDuckOrWhatever, value);
return;
}
//No property with this name was found, fall back to field
var field = typeOfObject.GetField(attributeName);
if (field == null) throw new Exception("No property or field was found on type '" + typeOfObject.FullName + "' by the name of '" + attributeName + "'.");
field.SetValue(chickenDuckOrWhatever, value);
}
缓存FieldInfo和PropertyInfo,并在反映之前先查阅字典。
提示:要不必将chickenDuckOrWhatever
硬编码为字符串,您可以使用C#6功能nameof。比如名称(Chicken.AttributeName),例如。