【问题标题】:Cast object from an interface to its implementation将对象从接口转换为实现
【发布时间】:2017-09-06 14:39:09
【问题描述】:

我有以下代码:

public interface BaseInterface
{
    int ID { get; }
}

public interface SpecialInterface1 : BaseInterface
{
    int price { get; }
}

public interface SpecialInterface1 : BaseInterface
{
    int xyz { get; }
}

public class Implementation1 : SpecialInterface
{
    int price { get; }
    int ID { get; internal set; }
}

public class Implementation2 : SpecialInterface
{
    int xyz { get; }
    int ID { get; internal set; }
}

现在在管理类中,我想将实现BaseInterface 的对象添加到列表中。

我知道我可以使用 asis 将接口转换为实现,但在我的项目中,我有大约 10 个特殊接口,每个接口都有一个实现,所以我必须编写一个非常大的 if 语句.

public void Add(BaseInterface u, int id)
{
    if (u is Implementation1)
    {
        ((Implementation1)u).ID = id;
        Units.Add(u);
    }

    if (u is Implementation2)
    {
        ((Implementation2)u).ID = id;
        Units.Add(u);
    }
}

我的目标是 id 在实现之外是不可更改的,我将只提供我的 dll 之外的接口,因此没有人可以更改 id。

【问题讨论】:

  • 您可以在任何情况下强制转换为 BaseInterface,因为它声明了 ID。不需要任何如果。
  • @sam 但是你需要一个强制转换来将接口的一个实例变成具体的实现,这就是这里正在做的事情。
  • @Servy 是的,但你不需要这样做。
  • @sam 是的,在显示的代码中确实如此。
  • 您可以创建一个实现 BaseInterface 的 BaseImplementation 类,并为 ID 属性添加内部设置器,并使每个进一步的实现也从 BaseImplementation 继承,然后您可以将 Implementation 转换为您的 BaseImplementation并在您的管理类中设置 ID

标签: c# .net interface casting


【解决方案1】:

一个解决方案是添加一个额外的接口。这消除了实现中的内部设置器。

internal interface IChangeID
{
    void SetID(int id);
}

public interface IBaseInterface
{
    int ID { get; }
}

public class Implementation : IBaseInterface,
                              IChangeID
{        
    public void SetID(int id) { ID = id; }
    public int ID { get; private set; }
}

只有真正的实现才能实现IChangeID。返回IBaseInterfaceISpecialInterface 将隐藏setter,因为这些接口不继承自IChangeID

这会将您的添加更改为:

public void Add(BaseInterface u, int id)
{
     ((IChangeID)u).SetID(id);
     Units.Add(u);
}  

如果您确实想返回具体类型,而不是接口。您可以显式实现给定的接口。即使在具体实现中,这也会隐藏 set 方法。

public class Implementation : IBaseInterface,
                              IChangeID
{        
    void IChangeID.SetID(int id) { ID = id; }
    public int ID { get; private set; }
}

var obj = new Implementation();
obj.SetID() // This WILL NOT Compile

【讨论】:

  • 什么?您如何在界面上使用internal?那甚至无法编译。
  • github.com/dotnet/csharplang/blob/master/spec/…github.com/dotnet/csharplang/blob/master/spec/… - 内部应该完全没问题,我在发布前对其进行了测试,可以编译。
  • 看什么? internal set 不会在接口属性中编译
  • 抱歉,我看不出这些链接的含义。如果您考虑一下,在界面上将某些内容声明为 internal 甚至都没有意义。
  • links 是关于 interfacetype 修饰符的。不是属性设置器。你可以有一个内部类型或接口,但不能有一个内部集
【解决方案2】:

如果您不想想要修改接口和实现,您可以使用 C# 7 的模式匹配来访问实现类型而无需强制转换。每个实现类型需要 3 行,但避免修改类:

public void Add(BaseInterface u, int id)
{
    switch(u)
    {
        case Implementation1 u1:
            u1.ID = id;
            break;
        case Implementation2 u1:
            u1.ID = id;
            break;    
        default :
            throw new ArgumentException("Unexpected implementation!");
    }
    Units.Add(u);     
}  

明显的缺点是,如果添加新的实现,则必须修改代码。

另一种选择是使用dynamic,这会失去类型安全性。如果某些实现没有设置器(例如,因为它被构造函数初始化替换),这将在运行时失败

public void Add(BaseInterface u, int id)
{
    dynamic x =u;
    x.ID=id;
    Units.Add(x);     
} 

【讨论】:

  • C#7 “模式匹配”(空中引号)是唯一合理的解决方案。
  • @ChrisMarisic 取决于要求。 “作家”界面更安全
  • 所有实现都有一个内部可设置的 ID。动态的使用似乎对我的项目有效,但我不能 100% 确定我是否应该使用它。
  • 感谢您的反对。有什么特别的原因还是只是觉得脾气暴躁?
【解决方案3】:

虽然我最喜欢this answer,但

我建议将 ID 作为所有实现的构造函数的必需参数,然后使用工厂模式生成您需要的任何实例。这使得任何没有 ID 集的实例在编译时而不是运行时抛出异常,从而降低了发生异常的概率。

这是一个简单的示例,无需额外的界面即可为您提供所需的内容。如果您选择,您可以将我的答案与@Iqon 的答案结合起来。

public interface IInterface
{
    int ID { get; }
}

internal class InternalImplementation: IInterface {
    public InternalImplementation(int ID) { this.ID = ID; }
    public int ID { get; set; }
}

public class MyImplementationFactoryService {

    public IInterface Create() {
        int id = 1 // Or however you get your ID, possibly from a DB query?
        return new InternalImplementation(id);
    }

    public IInterface Create(type|enum createtype) {
        // return type based on typeof or enum
    }

}

【讨论】:

  • 这将阻止您的库的使用者可以创建对象。但如果我理解正确,您的库将管理这些对象,这意味着您的库也应该是创建者。
  • 是和不是。消费者将无法更新任何内部实现,但一个简单的工厂方法可以解决这个问题。但是,也没有什么可以阻止消费者尝试使用自己的实现。
【解决方案4】:

如果您想使用反射来设置属性,下面的代码可能会有所帮助

public interface IBaseInterface
{
   int ID { get; }
}

public class Impl1 : IBaseInterface 
{
    public int ID { get; internal set; }

    public int Price {get; set;}
}

public class Impl2 : IBaseInterface 
{
    public int ID { get { return 0;} }

    public int Subscription {get; set;}
}

public class Program
{
    public static void Main(string[] args)
    {
        IBaseInterface obj1 = new Impl1();

        SetProperty(obj1, "ID", 100);
        Console.WriteLine("Object1 Id is {0}", obj1.ID); 

        IBaseInterface obj2 = new Impl2();

        SetProperty(obj2, "ID", 500);
        Console.WriteLine("Object2 Id is {0}", obj2.ID); 
    }

    private static void SetProperty(IBaseInterface obj, string propertyName, object id){
        if(obj.GetType().GetProperty(propertyName).CanWrite) {
            obj.GetType().GetProperty(propertyName).SetValue(obj, id); 
            Console.WriteLine("CanWrite property '{0}' : {1}" , propertyName, obj.GetType().GetProperty(propertyName).CanWrite); 
        }
    }
}

输出

CanWrite 属性“ID”:真

Object1 ID 为 100

Object2 Id 为 0

【讨论】:

  • 我不会推荐这个解决方案,因为整个想法是让消费者无法设置 ID。
  • @BrianPickens 不确定我是否理解您的评论,但是此代码不允许消费者设置值。此代码背后的意图是使用反射而不是类型检查来设置值。除了使用类型转换到具有可写 ID 的 Class 使用反射 SetValue 之外,一切都与上述其他解决方案相同。
猜你喜欢
  • 1970-01-01
  • 2019-07-31
  • 1970-01-01
  • 2019-05-08
  • 2015-04-11
  • 2010-12-08
  • 2011-04-11
相关资源
最近更新 更多