C# Explicit operator and Object

Viewed 4659

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

4 Answers

For a truly overengineered solution combining Chris Sinclair's answer using reflection with his memoized conversion methods in order to maintain performance while also not needing to call ConversionUtility.RegisterConversion()...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public static class ConvertUtil
{
    /// <summary>
    /// Converts <paramref name="source"/> to type <typeparamref name="TResult"/> 
    /// using reflection to determine the appropriate explicit or implicit cast
    /// operator.
    /// </summary>
    /// <typeparam name="TResult">
    /// The Type to convert <paramref name="source"/> to.
    /// </typeparam>
    /// <param name="source">The object being converted.</param>
    /// <returns>
    /// <paramref name="source"/> cast as <typeparamref name="TResult"/>.
    /// </returns>
    /// <remarks>
    /// Detects the type of <paramref name="source"/> type at run time.  This is a
    /// minor performance hit if used frequently.
    /// </remarks>
    /// <exception cref="InvalidCastException" />
    public static TResult ConvertTo<TResult>(object source) where TResult : class
    {
        if (source == null || sosurce is TResult)
        {
            return source;
        }

        Type sourceType = source?.GetType();
        return (TResult)GetConverter(sourceType, typeof(TResult))?.Invoke(source) ??
            throw new InvalidCastException($"No implicit or explicit cast from type" +
                $" {sourceType.Name} to {resultType.Name} exists.");
    }

    /// <summary>
    /// Converts <paramref name="source"/> to type <typeparamref name="TResult"/> 
    /// using reflection to determine the appropriate explicit or implicit cast
    /// operator.
    /// </summary>
    /// <typeparam name="TSource">
    /// The type <paramref name="source"/> is being converted from.
    /// </typeparam>
    /// <typeparam name="TResult">
    /// The Type to convert <paramref name="source"/>
    /// to.
    /// </typeparam>
    /// <param name="source">The object being converted.</param>
    /// <returns>
    /// <paramref name="source"/> cast as <typeparamref name="TResult"/>.
    /// </returns>
    /// <remarks>
    /// Detects the type of <paramref name="source"/> type at compile time for a
    /// slight performance gain over <see cref="ConvertTo{TResult}(object)"/> if used
    /// frequently.
    /// </remarks>
    /// <exception cref="InvalidCastException" />
    public static TResult ConvertTo<TSource, TResult>(TSource source) 
        where TSource : class 
        where TResult : class
    {
        if (source == null || sosurce is TResult)
        {
            return source;
        }

        Func<object, object> converter = 
            GetConverter(typeof(TSource), typeof(TResult)) ??
                throw new InvalidCastException($"No implicit or explicit cast from " +
                    $"type {sourceType.Name} to {resultType.Name} exists.");
        return (TResult)converter(source);
    }

    /// <summary>
    /// Lock for thread safety.  If this isn't an issue, you can remove this and all
    /// the <c>lock</c> portions of <see cref="GetConverter(Type, Type)"/>
    /// </summary>
    private static readonly object s_typeConvertersLock = new object();

    /// <summary>
    /// The map from source types to maps from destination types to their converting
    /// functions.
    /// </summary>
    private static readonly Dictionary<Type, Dictionary<Type, Func<object, object>>> s_typeConverters =
        new Dictionary<Type, Dictionary<Type, Func<object, object>>>();

    /// <summary>
    /// Retrieves the first implicit or explicit defined casting operator from
    /// <paramref name="sourceType"/> to <paramref name="resultType"/>.  Returns null
    /// if no such operator is defined.
    /// </summary>
    /// <param name="sourceType">The type being converted from.</param>
    /// <param name="resultType">The type being converted to.</param>
    /// <remarks>
    /// Only searches for the cast operator once per (<paramref name="sourceType"/>,
    /// <paramref name="resultType"/>) pair.
    /// </remarks>
    /// <returns>
    /// The first defined casting operator from <paramref name="sourceType"/> to
    /// <paramref name="resultType"/>. Null if no operator is defined.
    /// </returns>
    private static Func<object, object> GetConverter(Type sourceType, Type resultType)
    {
        // retrieve all the operators from sourceType that we know about if we know of
        // none, add an empty map to the dictionary
        if (!s_typeConverters.TryGetValue(sourceType, 
                out Dictionary<Type, Func<object, object>> sourceConverters))
        {
            lock (s_typeConvertersLock)
            {
                // check again in case another thread has already added the converter
                // dictionary while waiting for the lock
                if (!s_typeConverters.TryGetValue(sourceType, out sourceConverters))
                {
                    sourceConverters = new Dictionary<Type, Func<object, object>>();
                    s_typeConverters.Add(sourceType, sourceConverters);
                }
            }
        }

        // retrieve the operator from sourceType to resultType 
        // if we have not found it yet, search for it using reflection and add it to
        // the dictionary 
        // if no such cast operator exists, add still null to the dictionary so that
        // we don't need to search again
        if (!sourceConverters.TryGetValue(resultType, 
                out Func<object, object> converter))
        {
            lock (s_typeConvertersLock)
            {
                // check again in case another thread has already added the converter
                // while waiting for the lock
                if (!sourceConverters.TryGetValue(resultType, out castOperator))
                {
                    var castOperator =
                        (from method in resultType.GetMethods(BindingFlags.Static | BindingFlags.Public)
                         where (method.Name == "op_Explicit" | 
                                method.Name == "op_Implicit") &&
                             method.ReturnType == resultType
                         let paramInfo = method.GetParameters()
                         where paramInfo.Length == 1 && 
                            paramInfo[0].ParameterType == sourceType
                         select method).FirstOrDefault();

                    converter = 
                        source => castOperator?.Invoke(null, new object[] { source });
                    sourceConverters.Add(resultType, converter);
                }
            }
        }

        return converter;
    }
}

The reason this method is helpful (at least to me) is that Linq's IEnumerable<T>.Cast<TResult>() doesn't handle implicitly or explicitly defined casting operators, only base class or interface to subclass. I wanted to define my own overload of Cast (IEnumerable<T>.Cast<T, TResult>()) but found it troublesome because there was no way to tell the compiler that T can be cast to TResult. Given the above, this was my solution:

namespace System.Linq
{
    using System.Collections.Generic;

    public static class LinqExtensions
    {
        public static IEnumerable<TResult> Cast<T, TResult>(this IEnumerable<T> self)
        {
            return self.Select(item => ConvertUtil.Convert<T, TResult>(item));
        }
    }
}

Unfortunately, C# doesn't allow partial inference of generic functions so I actually have to specify T when calling myEnum.Cast<MyType, MyOtherType>().

As an aside, since I, personally, never call ConvertUtil.ConvertTo() in my code, I moved the memoization code into my extensions class, and put the code from ConvertUtil.ConvertTo<TSource, TResult>() directly in my IEnumerable<T>.Cast<T, TResult>() method. The primary reason for this is so that ConvertUtil.Convert() isn't called once per item--only once per IEnumerable<T>.Cast call. Of course, it's worth noting that usually myIEnum.Cast<MyType1, MyType2>() is longer than myIEnum.Select(i => (MyType2)i), so there's that.

Related