简体   繁体   English

C#显式运算符和对象

[英]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. 我相信这是因为它在Object类中搜索操作数而不是实际类型。 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 我真的想避免使用.Net4 / 4.5

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 . 这将引用V作为一个object (我们通常知道它) - 然后分别object转换为Point

Since Point is a struct , this then attempts to "unbox" that same reference as though it were a boxed Point . 由于Point是一个struct ,因此它尝试“取消装箱”相同的引用,就好像它是一个盒装的Point But it is not a boxed Point , so the error is correct. 但它不是盒装Point ,所以错误是正确的。 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. 但是,即使Point是一个class ,它仍会因类似错误而失败:如果类型不兼容,则引用保留转换也不起作用。

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. 如果您想避免使用dynamic因为您不想使用.NET 4.0的DLR,则可以使用反射来自行查找转换运算符。 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: 或者对于带有LINQ的.NET 3.5:

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. 如果您希望通过隐式运算符进行转换,也可以添加“op_Implicit”。


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. 第二个可能会更稳定,因为您只注册了您希望使用的转换,并且没有反射,只有转换和字典查找。

这是因为你将它转换为object而你没有明确的转换 - 它不会隐含地假设你希望它像float2D

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() ... 对于一个真正过度设计的解决方案,将Chris Sinclair使用反射的答案与他的memoized转换方法相结合,以保持性能,同时也不需要调用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. 这个方法有用的原因(至少对我而言)是Linq的IEnumerable<T>.Cast<TResult>()不处理隐式或显式定义的转换操作符,只处理基类或子类接口。 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 . 我想定义我自己的Cast重载( IEnumerable<T>.Cast<T, TResult>() ),但发现它很麻烦,因为没有办法告诉编译器T可以IEnumerable<T>.Cast<T, TResult>()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>() . 不幸的是,C#不允许对泛型函数进行部分推理,因此我实际上必须在调用myEnum.Cast<MyType, MyOtherType>()时指定T

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. ConvertUtil.ConvertTo() ,因为我个人从不在我的代码中调用ConvertUtil.ConvertTo() ,我将memoization代码移动到我的扩展类中,并将ConvertUtil.ConvertTo<TSource, TResult>()的代码直接放入我的IEnumerable<T>.Cast<T, TResult>()方法。 The primary reason for this is so that ConvertUtil.Convert() isn't called once per item--only once per IEnumerable<T>.Cast call. 这样做的主要原因是每个项目不会调用一次ConvertUtil.Convert() - 每个IEnumerable<T>.Cast调用只调用一次。 Of course, it's worth noting that usually myIEnum.Cast<MyType1, MyType2>() is longer than myIEnum.Select(i => (MyType2)i) , so there's that. 当然,值得注意的是,通常myIEnum.Cast<MyType1, MyType2>()myIEnum.Select(i => (MyType2)i) ,所以就是这样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM