界面铸造

时间:2016-08-19 11:57:22

标签: c# interface

我有一些课程,我无法改变。他们有一个共同的属性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;

5 个答案:

答案 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类型,c2dc3d,如果您获得类型的对象将无法工作c1c2c3

您可以像@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;} 

下行是一些重复的代码,但好处是没有例外和编译时类型检查。