将基类显式转换为派生类

时间:2017-02-27 16:31:23

标签: c# inheritance interface casting

问题:我有2种类型,它们是DB中2个不同程序的结果集: Proc1Result,Proc2Result (我们不得不将它们分开 - 但它们与输入/输出的基本相同)

然后我决定在运行时使用接口在需要的程序之间切换 - 但这意味着我需要一个可以转换的常用类型 Proc1Result和Proc2Result

这样我就不需要维护这个新类了(创建所有属性,如果DB结果发生任何变化就添加/删除) - 我从其中一个结果中派生出这个类:

public class DerivedClassForInterface : Proc1Result {}

然后我从第二个proc实现了显式转换,它工作正常,但是当我想实现从基类到派生类的显式转换时 - 它不允许我(因为它有点已经“做” - 但它在运行时失败) :

public class DerivedClassForInterface : Proc1Result 
{
    //ok - and works as expected
    public static explicit operator DerivedClassForInterface(Proc2Result v)
    {
        return new DerivedClassForInterface
        {
            ...
        };
    }

    //fail: 'user-defined' conversations to or from a base class are not allowed
    public static explicit operator DerivedClassForInterface(Proc1Result v)
    {
        return new DerivedClassForInterface
        {
            ...
        };
    }
}

这样可行:

//result2 is of type Proc1Result
DerivedClassForInterface castedResult = (DerivedClassForInterface)result2;
//compiles - works as expected at runtime

但这不是:

//result1 is of type Proc1Result
DerivedClassForInterface castedResult = (DerivedClassForInterface)result1;
//compiles - conversation fails at runtime

那么为什么我不能编写自己的显式运算符,如果你不能从基类转换为派生类?

有趣的是编译器允许我从基类转换为派生类,但它在运行时不起作用。

我可能只是为了简单的功能而为我做“铸造”。任何人都可以建议一个更好的解决方案(请记住,我希望保持“DerivedClassForInterface”遵守“Proc1Result”(或“Proc2Result” - 无所谓)的变化)

修改
@Peter Duniho - 这里的类型“Proc1Result”和“Proc2Result”是作为存储过程(linq2sql)的结果生成的。我希望有一个代码,当这些程序的输出发生变化时我不需要触摸(因为我们需要分割一堆程序 - 并且实现新模块可以并且通常会添加更多输出)。

Proc1和Proc2基本上是相同的存储过程(它们需要完全相同的输入并提供相同的输出(类型而不是数据))。它们都适用于不同的数据段,需要分开。

很抱歉让这个令人困惑(在我工作日结束时......)并且没有澄清 - 这里的问题实际上是:

为什么编译器允许我在运行时导致异常时从基类转换为派生类?为什么我不能自己实现这个转换(...因为它已经有了 - 但它只是在运行时不起作用?)

所以从我的立场 - 它看起来如下:
  - 我无法实现此演员,因为它已经存在   - 但它注定不起作用

这是“最小,完整且可验证的代码示例”(感谢链接):

//results from stored procedures in database which got splitted appart (linq 2 sql)
class Proc1Result { }
class Proc2Result { }
//

class DerivedClassForInterface : Proc1Result
{
    public static explicit operator DerivedClassForInterface(Proc2Result v)
    {
        //this part would be exported in generic function
        var derivedClassInstance = new DerivedClassForInterface();
        var properties = v.GetType().GetProperties();
        foreach (var property in properties)
        {
            var propToSet = derivedClassInstance.GetType().GetProperty(property.Name);
            if (propToSet.SetMethod != null) propToSet.SetValue(derivedClassInstance, property.GetValue(v));
        }
        return derivedClassInstance;
    }
}

interface IProcLauncher
{
    DerivedClassForInterface GetNeededData();
}

class ProcLauncher1 : IProcLauncher
{
    public DerivedClassForInterface GetNeededData()
    {
        var dataFromDb = new Proc1Result();/*just ilustrative*/
        return (DerivedClassForInterface)dataFromDb;
    }
}

class ProcLauncher2 : IProcLauncher
{
    public DerivedClassForInterface GetNeededData()
    {
        var dataFromDb = new Proc2Result();/*just ilustrative*/
        return (DerivedClassForInterface)dataFromDb;
    }
}


class Program
{
    static void Main(string[] args)
    {
        bool causeInvalidCastException = true;

        IProcLauncher procedureLauncher;
        if (causeInvalidCastException) procedureLauncher = new ProcLauncher1();
        else procedureLauncher = new ProcLauncher2();

        var result = procedureLauncher.GetNeededData();
        Console.WriteLine("I got here!");
    }
}

这个想法是:
  - 如果程序输出发生变化,则无需更改任何代码   - 在运行时确定要使用的过程。
  - 将转换部分导出为通用功能   - 必须注射。

我可以解决这个问题 - 比方说 - 只需要一个通用函数来处理所有情况下的对话,但问题在于粗体。

2 个答案:

答案 0 :(得分:1)

我不太了解你的问题。您好像说编译器允许您编写您发布的代码,但它在运行时失败。这不是我的经历。我在基类的显式转换操作上遇到编译时错误:

  

错误CS0553:' Derived.explicit运算符Derived(Base1)':不允许在基类之间进行用户定义的转换

我觉得很清楚。至于为什么你不能写这样的代码,你必须要求语言设计者确切知道,但这对我来说似乎是一个合理的限制。已经存在从任何基类到该基类的派生类的安全的内置转换,只要基类实例实际上是派生类的实例。如果程序员被允许进行额外的转换,那将会令人困惑并且可能导致错误,更不用说使语言规范对转换/转换运算符的规则大大复杂化。

至于更广泛的问题,我不了解你所选择的方法。您正在通过正常的方式完全颠倒设计类。如果你有许多都有共享成员的类,你希望能够在某些上下文中将所有这些类视为相同,并且你希望能够只实现一次这些共享成员并在其他类之间共享它们,您可以将所有这些成员放在一个基类中,然后从该类中派生所有各种类型。

我甚至不知道您当前的方法如何解决这个问题:

  

因此我不需要维护这个新类(创建所有属性,如果DB结果发生任何变化,添加/删除)

由于Proc2Result不会继承Proc1Result,如果Proc1Result发生变化,您无论如何都必须更改Proc2Result才能进行匹配。和任何其他类似的类型。和DerivedClassForInterface类。而且你必须改变所有的显式运算符。怎么样更好?

我认为你更喜欢这样的东西:

class BaseClassForInterface
{
    // declare all shared members here
}

class Proc1Result : BaseClassForInterface { ... }
class Proc2Result : BaseClassForInterface { ... }

然后,对于每个新的Proc...Result类,您只需继承基类,无需重新编写成员,并且每个Proc...Result类的转换都是微不足道的。你甚至不需要使用铸造/转换操作员;语言已经知道如何从派生类隐式转换为基类,因为派生类基类。

事实上,这是使用OOP的标准方法。它是任何OOP语言最基本的功能之一。

如果这不能让您回到正轨,那么您需要改进问题,以便更清楚您正在做什么以及为什么这样做。您还需要提供一个好的Minimal, Complete, and Verifiable code example,清楚地说明您的问题,准确解释代码的作用以及您希望它做什么。

<强>附录:

感谢您的编辑。你的问题现在更具体和清晰。我仍然有疑问,但至少我理解真实的背景。

在我看来,您已经理解了问题的大部分基本答案:

  

为什么编译器允许我在运行时导致异常时从基类转换为派生类?为什么我不能自己实现这个转换(...因为它已经有了 - 但它只是在运行时不起作用?)

     

所以从我的立场 - 它看起来如下:
    - 我无法实现此演员,因为它已经存在     - 但它注定不起作用

即。是的,我相信语言不允许这样做,因为已经有一个内置演员,是的,你寻求的确切方法注定不起作用。

就这一部分而言:

  

这个想法是:
    - 如果程序输出发生变化,则无需更改任何代码     - 在运行时确定要使用的过程。
    - 将转换部分导出为通用功能     - 必须注射。

如果我理解第一点,这就是你继承一个存储过程类型的原因。这样您就可以免费获得财产声明。对我来说似乎有些狡猾,但我承认我理解这一动机。

正如我理解上述第三点以及您在帖子中的陈述后,您已经知道如何编写通用方法来进行转换。例如。类似的东西:

DerivedClassForInterface ConvertToClassForInterface<T>(T t)
{
    DerivedClassForInterface result = new DerivedClassForInterface();
    Type resultType = typeof(DerivedClassForInterface);
    PropertyInfo[] properties = typeof(T).GetProperties();

    foreach (var property in properties)
    {
        var propToSet = resultType.GetProperty(property.Name);
        if (propToSet.SetMethod != null)
        {
            propToSet.SetValue(result, property.GetValue(t));
        }
    }
    return result;
}

即。本质上是您在显式运算符中显示的代码(带有一些小的清理/优化)。或者你可能没有使用术语&#34; generic&#34;从字面上看,只是意味着&#34;通用目的&#34;。显然,上面的内容很少,真正受益于该方法的通用性;您可以像在显式运算符上一样轻松地在参数上使用GetType()

不幸的是,我不知道标准&#34;是否可以注射&#34; 适合这里。注射,怎么样?你的意思是你想在其他地方注入代码吗?或者你的意思是代码需要与AOP系统兼容,还是应用的其他形式的代码注入?

忽略那些我不理解的部分,我实际上只是利用编译器和运行时为我做了所有繁重的工作(包括缓存反射内容,在你的代码中会很慢) )。你可以写一个这样的类:

class Wrapper
{
    private dynamic _data;

    public string Value {  get { return _data.Value; } }

    public Wrapper(dynamic data)
    {
        _data = data;
    }
}

考虑到其他几个类:

class Result1
{
    public string Value { get; set; }
}

class Result2
{
    public string Value { get; set; }
}

然后你可以像这样使用它:

Result1 r1 = new Result1 { Value = "result 1" };
Result2 r2 = new Result2 { Value = "result 2" };
Wrapper w1 = new Wrapper(r1), w2 = new Wrapper(r2);

Console.WriteLine("w1 result: " + w1.Value);
Console.WriteLine("w2 result: " + w2.Value);

即。只需创建Wrapper的实例,传递相关对象(在您的情况下,这将是存储过程中生成的类型)。当然,缺点是您必须向Wrapper类型添加属性以匹配您的存储过程。但我不相信这是件坏事。即使您以某种方式对其进行了排列,以便其余代码都不必更改,但这是一项相对较小的维护任务。

我怀疑改变存储过程需要在代码中的其他地方进行更改,以明确引用属性。因为毕竟,如果代码的其余部分对于特定的类成员同样完全不可知(即一直使用反射),那么你可以将结果对象作为object类型传递,而不用担心包装器。

答案 1 :(得分:1)

我按以下方式实施了转换:

class BaseConverter
{
    protected T Convert<T, X>(X result)
    {
        var derivedClassInstance = Activator.CreateInstance<T>();
        var derivedType = derivedClassInstance.GetType();

        var properties = result.GetType().GetProperties();
        foreach (var property in properties)
        {
            var propToSet = derivedType.GetProperty(property.Name);
            if (propToSet.SetMethod != null)
            {
                propToSet.SetValue(derivedClassInstance, property.GetValue(result));
            }
        }
        return derivedClassInstance;
    }

    protected List<T> Convert<T, X>(List<X> listResult)
    {
        var derivedList = new List<T>();
        foreach (var r in listResult)
        {
            //can cope with this - since there will not ever be many iterations
            derivedList.Add(Convert<T, X>(r));
        }
        return derivedList;
    }
}

因此接口实现类将继承它:

class ProcLauncher2 : BaseConverter, IProcLauncher
{
    public DerivedClassForInterface GetNeededData()
    {
        var dataFromDb = new Proc2Result();/*just ilustrative*/
        //usage (works for single result or list if I need a list returned):
        return Convert<DerivedClassForInterface, Proc2Result>(dataFromDb);
    }

    //other methods...
}

然而 - 我不清楚 - 为什么已经从基类转换为派生 - 如果这不起作用。 Imo - 它不应该存在并在编译时抛出错误。