2017-02-27 116 views
2

问题:我有2种类型的是结果集的2个不同的程序DB: Proc1Result,Proc2Result(我们不得不分开他们 - 但它们基本上是相同自带输入/输出)明确投基类派生类

然后我决定,我可以利用在运行时需要的程序之间切换的界面 - 但这只是意味着我需要1种常见的类型,而我可以转换Proc1Result和Proc2Result

所以我不需要维护这个新类(创建所有的属性,添加/删除,如果一个在DB结果nything变化) - 我得到的这个类从结果之一:

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!"); 
    } 
} 

当时的想法是:
- 无需更改任何代码,如果程序改变输出。
- 在运行时决定使用哪个proc。
- 将转换部分导出为通用功能。
- 被注射。

我可以解决这个问题 - 比如说 - 只需要一个泛型函数来处理所有情况下的对话,但问题以粗体显示。

+0

'Proc2Result'定义在哪里?我只看到'Proc1Result'。 –

+0

我对你的问题有点遗憾,但是如果我正确理解你的问题,那你为什么不把Proc1Result和Proc2Result之间的所有公共领域复制到一个通用接口。你能告诉你哪些类可以修改,哪些不能。你也可以用一些代码来演示你的问题(不是带有演员操作符的解决方案) – Vikhram

+0

@ rory.ap - Proc1Result和Proc2Result是由存储过程产生的Link2Sql分类生成的。最初的要求是将他们在两个不同的过程中进行分割(不在1个过程中)。最初我想通过强制转换(出于某种原因,编译器允许我将基类的实例obj转换为派生 - 但在运行时失败)。 我可以在2,3 ..等更多的方式解决问题 - 但我想知道为什么编译器让我这样做 - 但运行时没有。 – RUKAclMortality

回答

0

我实现了转换方式如下:

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... 
} 

然而 - 目前尚不清楚,我 - 为什么已经从投基类派生 - 如果这不起作用。伊莫 - 它不应该在那里,并在编译时抛出错误。

1

我不明白你的问题。您似乎认为编译器允许您编写您发布的代码,但它在运行时会失败。这不是我的经历。我得到基类的显式转换操作的编译时间错误:

错误CS0553:“Derived.explicit操作者衍生(基础1)”:用户定义的转换或从基类是不允许

似乎对我很清楚。至于你为什么不被允许编写这样的代码,你必须要求语言设计师确定地知道,但这对我来说似乎是一个合理的限制。只要基类实例实际上是派生类的实例,就已经有了从基类到该基类的派生类的安全的内置转换。如果程序员被允许进行额外的转换,这可能会引起混淆,并可能导致错误,而不必考虑使铸造/转换操作符的语言规范的规则复杂化。

至于更广泛的问题,我不明白你选择的方法。你正在设计的课程完全颠倒了人们通常会这样做的方式。如果您有许多类都具有共享成员,那么您希望能够在某些上下文中将所有这些类视为相同,并且您希望能够仅将这些共享成员实现一次并在其他类中共享它们,你会把所有这些成员放在一个基类中,然后派生出这个类的所有不同类型。

我甚至不看这种担心是如何你目前的做法地址:

只是让我不需要保持这个新类(创建所有属性,添加/删除,如果在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,它清楚地说明了您的问题,并精确地解释了代码的作用以及您希望它执行的操作。

附录:

感谢您的编辑。你的问题现在更具体和明确。我仍然有疑问,但至少我了解真实的情况。

在我看来,你已经了解最基础的回答你的问题:

为什么编译器让我从基础转换为派生类中运行时产生的异常?为什么我无法实现这个铸造自己(...因为它还挺已经没有 - 但它只是在运行时不工作?)

从我站的地方

所以 - 它看起来如下:
- 我可以由于它已经存在,所以没有执行此演职员表
- 但它注定不能工作

iee是的,我相信这种语言不允许这样做,因为已经有一个内置的演员阵容,是的,你寻求的确切方法注定是行不通的。

至于这部分云:

当时的想法是:
- 无需更改任何代码,如果程序改变输出。
- 在运行时决定使用哪个proc。
- 将转换部分导出为通用功能。
- 被注射。

如果我没有理解第一点,这就是为什么你继承了存储过程类型一个。这样你就可以免费获得财产声明。对我来说似乎有点冒险,但我承认我确实了解动机。

正如我所理解的上述第三点和您在发布后的声明,您已经知道如何编写通用方法来完成转换。例如。例如:

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; 
} 

即,基本上是你在显式操作符中显示的代码(有一些小的清理/优化)。或者,也许你没有直接使用“通用”一词,而只是指“通用”。很显然,上述方法很少从通用方法中获益,您可以像使用显式运算符一样轻松地在参数上使用GetType()

不幸的是,我不知道标准“是注射”适合在这里。如何注射?你的意思是你想要在其他地方注入代码吗?或者你的意思是代码需要与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类型,而不用担心包装器。

+0

感谢您的重播。这就是你写的所有非常好的东西,通常这就是我所做的,但这里Proc1Result和Proc2Result是从存储过程生成的类。我用更多的解释和可测试的代码编辑了原文。 – RUKAclMortality

+0

@RUKAclMortality:好的,我试图根据您的附加信息扩展我的答案。请参阅上面的修改。 –

+0

@ Peter Duniho - 感谢您的提示。 1)对于“注入”,我的意思是你需要能够注入实现的接口类的实例,无论你需要它(通过函数作为参数fe)2)是 - 与“通用”我的意思是实际上“通用”我已经添加了我的实现作为答案3)事情是 - 这个代码是在一个图书馆,然后“分布”,并在其他程序中使用。 – RUKAclMortality