I have the following class:
class CrmToRealTypeConverter : IConverter
{
#region IConverter Members
public object Convert<T>(T obj)
{
return Convert(obj);
}
#endregion
private DateTime? Convert(CrmDateTime obj)
{
return obj.IsNull == false ? (DateTime?)obj.UserTime : null;
}
private int? Convert(CrmNumber obj)
{
return obj.IsNull == false ? (int?)obj.Value : null;
}
private decimal? Convert(CrmDecimal obj)
{
return obj.IsNull == false ? (decimal?)obj.Value : null;
}
private double? Convert(CrmDouble obj)
{
return obj.IsNull == false ? (double?)obj.Value : null;
}
private float? Convert(CrmFloat obj)
{
return obj.IsNull == false ? (float?)obj.Value : null;
}
private decimal? Convert(CrmMoney obj)
{
return obj.IsNull == false ? (decimal?)obj.Value : null;
}
private bool? Convert(CrmBoolean obj)
{
return obj.IsNull == false ? (bool?)obj.Value : null;
}
}
I am tryi开发者_如何学运维ng to specialize the Convert method with concreate types.
Currently it just loops recursively inConvert<T>()
until a stack overflow occurs.Polymorphism doesn't work on arguments to a method call. An approach you can use it to check the type of obj, cast it to the specific type and then call the appropriate overload.
public object Convert(object obj)
{
if (obj is CrmDateTime)
return Convert((CrmDateTime)obj);
if (obj is CrmNumber)
return Convert((CrmNumber)obj);
// ...
}
Late-binding doesn't happen the way you think it does; the compiler binds the call to Convert(obj)
in thepublic object Convert<T>(T obj)
method to the same method (recursive call). The behaviour you appear to be expecting is that the CLR will dynamically choose the most appropriate overload to execute at run-time, but it doesn't work that way. Try something like this instead:
public object Convert<T>(T obj)
{
if (obj == null)
throw new ArgumentNullException("obj");
var cdt = obj as CrmDateTime;
if (cdt != null)
return Convert(cdt); // bound at compile-time to DateTime? Convert(CrmDateTime)
var cn = obj as CrmNumber;
if (cn != null)
return Convert(cn); // bound at compile-time to int? Convert(CrmNumber)
// ...
throw new NotSupportedException("Cannot convert " + obj.GetType());
}
If you prefer, you can use reflection here. Such a solution would look something like:
// Making the method generic doesn't really help
public object Convert(object obj)
{
if (obj == null)
throw new ArgumentNullException("obj");
// Target method is always a private, instance method
var bFlags = BindingFlags.Instance | BindingFlags.NonPublic;
// ..which takes a parameter of the obj's type.
var parameterTypes = new[] { obj.GetType() };
// Get a MethodInfo instance that represents the correct overload
var method = typeof(CrmToRealTypeConverter)
.GetMethod("Convert", bFlags, null, parameterTypes, null);
if (method == null)
throw new NotSupportedException("Cannot convert " + obj.GetType());
// Invoke the method with the forwarded argument
return method.Invoke(this, new object[] { obj });
}
The model you should follow is the model in the .Net Convert class, there is no reason for you to make the constructor a generic, it brings nothing to the table. Change the convert routines to static methods and the class itself to static:
static class CrmToRealTypeConverter : IConverter
{
#region IConverter Members
public static DateTime? Convert(CrmDateTime obj)
{
return obj.IsNull == false ? (DateTime?)obj.UserTime : null;
}
public static int? Convert(CrmNumber obj)
{
return obj.IsNull == false ? (int?)obj.Value : null;
}
public static decimal? Convert(CrmDecimal obj)
{
return obj.IsNull == false ? (decimal?)obj.Value : null;
}
public static double? Convert(CrmDouble obj)
{
return obj.IsNull == false ? (double?)obj.Value : null;
}
public static float? Convert(CrmFloat obj)
{
return obj.IsNull == false ? (float?)obj.Value : null;
}
public static decimal? Convert(CrmMoney obj)
{
return obj.IsNull == false ? (decimal?)obj.Value : null;
}
public static bool? Convert(CrmBoolean obj)
{
return obj.IsNull == false ? (bool?)obj.Value : null;
}
}
Then when you call one of the convert methods the compiler will select the proper overload to call:
CrmDateTime crmDate;
CrmToRealTypeConverter.Convert(crmDate); // Will call the static DateTime? Convert(CrmDateTime obj) overload
// or
CrmNumber crmNum;
CrmToRealTypeConverter.Convert(crmNum); // Will call the static int? Convert(CrmNumber obj) overload
// and so on...
Edit: If you do the following:
CrmFloat num;
// ...
Object obj = num;
CrmToRealTypeConverter.Convert(obj);
it will not work as the compiler doesn't know the type to match the overload. You would have to cast it and it will work:
CrmToRealTypeConverter.Convert((CrmFloat)obj);
This occurs because the compiler doesn't know the generic type of T
until runtime and binds the call to T = System.Object
at compile-time, and the only function suitable to take a System.Object is that function itself. However, in .NET 4, you can use the dynamic
keyword to cause the runtime to select the correct overload dynamically based on T at runtime, which is what you are looking to happen.
Simple example:
class Main {
static void somefunction(System.String str)
{
System.Console.WriteLine("In String overload");
}
static void somefunction(System.Object obj)
{
System.Console.WriteLine("In Object overload");
}
static void somegenericfunction<T>(T object)
{
somefunction(object);
}
static void dynamicfunction<T>(dynamic T object)
{
somefunction(object);
}
static void main(System.String[] args)
{
somegenericfunction("A string"); // Calls Object overload, even though it's a String.
dynamicfunction("A string"); // Calls String overload
}
}
Note that I don't actually have my compiler on hand and this might not compile literally, but close enough.
精彩评论