问题:我有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!");
}
}
这个想法是:
- 如果程序输出发生变化,则无需更改任何代码
- 在运行时确定要使用的过程。
- 将转换部分导出为通用功能
- 必须注射。
我可以解决这个问题 - 比方说 - 只需要一个通用函数来处理所有情况下的对话,但问题在于粗体。
答案 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 - 它不应该存在并在编译时抛出错误。