Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Explicit operator and Object

QUESTION

Please take a look to the code first.

Here is my custom class:

public class float2D
{
    public float X { get; private set; }
    public float Y { get; private set; }

    public float2D(float x, float y)
    {
        this.X = x;
        this.Y = y;
    }

    public static explicit operator Point(float2D x)
    {
        return new Point((int)x.X, (int)x.Y);
    }

    ...
}

And here is the test code I wrote:

    private void TEST()
    {
        float2D V = new float2D(1, 1);
        Point P = Point.Empty;
        P = (Point)V; // Works
        P = (Point)(V as object); // Specified cast is not valid.
    }

As you can see it failed to convert the value when value type is not known. I believe this happens because it search in Object class for operand not in real type. How can I solve this problem?

I have a code where EVERYTHING is object and it must take care of these conversations.

Please tell me if you have any Idea.


AVOIDING DYNAMIC

Ok let change the sample in the way you can see what I exactly want to do and what is my situation.

Here is the class I have:

    class TEST
    {
        dynamic OBJECT;
        public void STORE<T>(ref T V)
        {
            this.OBJECT = V;
        }

        public T CONVERT<T>()
        {
            return (T)this.OBJECT;
        }
    }

And here is the test code:

        float2D V = new float2D(1, 1);
        TEST t = new TEST();
        t.STORE(ref V);
        Point P = t.CONVERT<Point>();

Is there way I can drop dynamic from above class and keep it working? I really want to avoid .Net4/4.5

like image 283
Soroush Falahati Avatar asked Dec 17 '13 13:12

Soroush Falahati


3 Answers

As stated by other answers, you can't do this because you're trying to apply runtime casting to compile time conversion operations.

If you want to avoid dynamic because you don't want to use .NET 4.0's DLR, you could use reflection to find the conversion operators yourself. Can't comment on performance for your particular application doing this however:

public static TOutgoing Convert<TOutgoing>(object obj)
{
    Type incomingType = obj.GetType();

    MethodInfo conversionOperator = null;
    foreach(var method in incomingType.GetMethods(BindingFlags.Static | BindingFlags.Public))
    {
        if (
            method.Name == "op_Explicit" && //explicit converter
            method.ReturnType == typeof(TOutgoing) && //returns your outgoing ("Point") type
            method.GetParameters().Length == 1 && //only has 1 input parameter
            method.GetParameters()[0].ParameterType == incomingType //parameter type matches your incoming ("float2D") type
            )
        {
            conversionOperator = method;
            break;
        }
    }

    if (conversionOperator != null)
        return (TOutgoing)conversionOperator.Invoke(null, new object[]{obj});

    throw new Exception("No conversion operator found");
}

Or for .NET 3.5 with LINQ:

public static TOutgoing Convert<TOutgoing>(object obj)
{
    Type incomingType = obj.GetType();

    var conversionOperator = incomingType.GetMethods(BindingFlags.Static | BindingFlags.Public)
        .Where(m => m.Name == "op_Explicit")
        .Where(m => m.ReturnType == typeof(TOutgoing))
        .Where(m => m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == incomingType)
        .FirstOrDefault();

    if (conversionOperator != null)
        return (TOutgoing)conversionOperator.Invoke(null, new object[]{obj});

    throw new Exception("No conversion operator found");
}

Usage:

float2D V = new float2D(1, 1);
Point P = Point.Empty;
P = Convert<Point>(V); //passes
P = Convert<Point>((V as object)); //passes

You can add "op_Implicit" as well if you wish to convert via implicit operators.


Another option if you want to avoid reflection is to pre-register the conversion functions, some casting and type lookups to determine which conversion operator to use.

Just a caveat, the solution here has a couple issues (thread safety, assumption that conversion functions exist, registering colliding/duplicate conversion functions throws errors) so use at your own risk or use it as a guide to modify as it suits your needs.

Basic gist is to define a simple converter to wrap the conversion functions themselves:

public interface IConverter
{
    object Convert(object incomingObject);
}

public class Converter<TIncoming, TOutgoing> : IConverter
{
    private Func<TIncoming, TOutgoing> ConversionFunction;

    public Converter(Func<TIncoming, TOutgoing> conversionFunction)
    {
        this.ConversionFunction = conversionFunction;
    }

    public object Convert(object incomingObject)
    {
        TIncoming typedIncomingObject = (TIncoming)incomingObject;
        return ConversionFunction(typedIncomingObject);
    }
}

Then a utility that you can register these conversions with:

public static class ConversionUtility
{
    private static Dictionary<Type, Dictionary<Type, IConverter>> Converters = new Dictionary<Type, Dictionary<Type, IConverter>>();

    public static void RegisterConversion<TIncoming, TOutgoing>(Func<TIncoming, TOutgoing> conversionFunction)
    {
        if (!Converters.ContainsKey(typeof(TIncoming)))
        {
            Converters[typeof(TIncoming)] = new Dictionary<Type, IConverter>();
        }

        Converters[typeof(TIncoming)].Add(typeof(TOutgoing), new Converter<TIncoming, TOutgoing>(conversionFunction));
    }

    public static TOutgoing Convert<TOutgoing>(object obj)
    {
        Type incomingType = obj.GetType();

        IConverter converter = Converters[incomingType][typeof(TOutgoing)];

        return (TOutgoing)converter.Convert(obj);
    }
}

For usage, first you must register the conversion functions you expect to use in your application (ideally perform the registration when your application starts up to avoid threading issues):

ConversionUtility.RegisterConversion((float2D obj) => (Point)obj);

Then your conversion usage:

float2D V = new float2D(1, 1);
Point P = Point.Empty;
P = ConversionUtility.Convert<Point>(V); //passes
P = ConversionUtility.Convert<Point>((V as object)); //passes

Not sure about the performance of one over the other for your particular application usage. The first sample is a bit more flexible as it performs the check at runtime and you don't have to pre-register the conversions you expect to use. The second might be a bit more stable as you only register the conversions you expect to use and there is no reflection, just casting and dictionary lookups.

like image 109
Chris Sinclair Avatar answered Oct 11 '22 08:10

Chris Sinclair


Yes, these two very different things. The first line:

P = (Point)V; // Works

uses the explicit conversion operator, which is overloaded for this combination. The second, however:

P = (Point)(V as object); // Specified cast is not valid.

this casts the reference V as an object (which we trivially know it is) - and then separately casts from object to Point.

Since Point is a struct, this then attempts to "unbox" that same reference as though it were a boxed Point. But it is not a boxed Point, so the error is correct. Conversion operators are not polymorphic, and unboxing is only allowed when the box contains a boxed copy of the appropriate type (caveat: you can unbox enums as integers, and integers as enums - as long as the underlying types match).

However, even if Point was a class, it would still fail with a similar error: reference-preserving casts also do not work if the types are incompatible.

like image 15
Marc Gravell Avatar answered Oct 11 '22 08:10

Marc Gravell


It is because you are casting it to object and you don't have an explicit cast for that - it won't implicitly assume you want it to be as a float2D.

like image 2
Daniel A. White Avatar answered Oct 11 '22 08:10

Daniel A. White