我有一些课程,我无法改变。他们有一个共同的属性Prop3
:
public class c1
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
public class c2
{
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
public class c3
{
public string Prop5 { get; set; }
public string Prop3 { get; set; }
}
现在我想在不知道类型的情况下访问此属性。我想过使用一个接口:
public interface ic
{
string Prop3 { get; set; }
}
但是这段代码会抛出一个无效的强制转换异常:
c1 c1o = new c1() { Prop3 = "test" };
string res = ((ic)c1o).Prop3;
答案 0 :(得分:11)
C#不支持编译时duck-typing,所以如果你不能改变你的类型,那就没有运气了。
您可以使用dynamic
访问您的属性,这允许运行时鸭子输入(但是没有编译时检查,如果您使用Visual Studio,则会丢失智能感知):
c1 c1o = new c1() { Prop3 = "test" };
string res = ((dynamic)c1o).Prop3;
或通过反思:
c1 c1o = new c1() { Prop3 = "test" };
string res = (string)c1o.GetType().GetProperty("Prop3").GetValue(c1o);
由于没有编译时检查,如果您传递的实例没有Prop3
,则需要处理异常。
或者如果类型没有密封,您可以尝试实现自己的派生类型,您可以在其中指定接口:
public interface ic
{
string Prop3 { get; set; }
}
public class c1d : c1, ic {}
public class c2d : c2, ic {}
public class c3d : c3, ic {}
这将要求您控制实例的创建,但实例需要是c1d
类型,c2d
,c3d
,如果您获得类型的对象将无法工作c1
,c2
或c3
您可以像@David指出的那样进行显式类型转换(这是一个聪明的技巧),但这意味着您将拥有对象的两个实例。对于像问题中提到的一个非常简单的情况,它可能会......如果你需要更高级的东西,那可能会非常棘手
答案 1 :(得分:7)
使用类似适配器的结构来封装转换逻辑。当然,这样做的缺点是你必须在c4
弹出时修改类。
public class Adapter {
public Adapter(object c) {
if (!(c is c1 || c is c2 || c is c3))
throw new NotSupportedException();
_c = c;
}
private readonly object _c;
public string Prop3 {
get {
if (_c is c1) return ((c1)_c).Prop3;
if (_c is c2) return ((c2)_c).Prop3;
if (_c is c3) return ((c3)_c).Prop3;
throw new NotSupportedException();
}
}
}
用法:
var c1o = new c1() { Prop3 = "test" };
var adapter1 = new Adapter(c1);
var res1 = adapter1.Prop3;
var c2o = new c2() { Prop3 = "test" };
var adapter2 = new Adapter(c2);
var res2 = adapter2.Prop3;
答案 2 :(得分:3)
另一种解决方法是使用反射来获取具有指定名称的属性值。
例如,编写一个简单的辅助方法,如下所示:
public static T GetProperty<T>(object obj, string name)
{
return (T) obj.GetType().GetProperty(name).GetValue(obj);
}
然后给出以下不相关的类:
public class C1
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
public class C2
{
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
public class C3
{
public string Prop5 { get; set; }
public string Prop3 { get; set; }
}
public class C4
{
public string Prop4 { get; set; }
public string Prop5 { get; set; }
}
您可以访问Prop3
属性,如下所示:
object c1 = new C1 {Prop3 = "C1"};
object c2 = new C2 {Prop3 = "C2"};
object c3 = new C3 {Prop3 = "C3"};
object c4 = new C4();
Console.WriteLine(GetProperty<string>(c1, "Prop3")); //Prints C1
Console.WriteLine(GetProperty<string>(c2, "Prop3")); //Prints C2
Console.WriteLine(GetProperty<string>(c3, "Prop3")); //Prints C3
Console.WriteLine(GetProperty<string>(c4, "Prop3")); // Throws an exception.
答案 3 :(得分:1)
因为它是无效的演员表。 c1
没有实现接口ic
:
public class c1
{
// ...
}
为了进行投射,对象必须具有合法的多态性:
public class c1 : ic
{
// ...
}
但如果你真的无法以任何方式改变课程,那么这些类型并不是多态的,而且完全不相关。要从一个更改为另一个,您需要手动转换:
public class someClass : ic
{
string Prop3 { get; set; }
}
//...
c1 c1o = new c1() { Prop3 = "test" };
string res = (new someClass { Prop3 = c1o.Prop3 }).Prop3;
这个例子当然是非常人为的。但是你明白了。两个完全不相关的类型不能从一个转换为另一个。你必须转换它们。
你可以以各种方式封装此转换。辅助方法,工厂,甚至是显式转换运算符(允许您最初尝试的转换)
public class someClass : ic
{
string Prop3 { get; set; }
public static explicit operator someClass(c1 c1o)
{
return new someClass { Prop3 = c1o.Prop3 };
}
}
// which should allow this...
c1 c1o = new c1() { Prop3 = "test" };
string res = ((ic)c1o).Prop3;
答案 4 :(得分:1)
与其处理反射/动态及其中的陷阱,您的方法可以很容易地分成3个重载:
public string GetString(c1 item){ return item.Prop3;}
public string GetString(c2 item){ return item.Prop3;}
public string GetString(c3 item){ return item.Prop3;}
下行是一些重复的代码,但好处是没有例外和编译时类型检查。