9

问题

请先看一下代码。

这是我的自定义类:

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);
    }

    ...
}

这是我写的测试代码:

    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.
    }

如您所见,当值类型未知时,它无法转换值。我相信这是因为它在 Object 类中搜索不是真实类型的操作数。我怎么解决这个问题?

我有一个代码,其中一切都是对象,它必须处理这些对话。

如果您有任何想法,请告诉我。


避免动态

好的,让我们以您可以看到我到底想要做什么以及我的情况的方式更改示例。

这是我的课程:

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

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

这是测试代码:

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

有没有办法我可以从上面的类中删除动态并保持它的工作?我真的很想避免 .Net4/4.5

4

4 回答 4

15

是的,这是两个非常不同的东西。第一行:

P = (Point)V; // Works

使用显式转换运算符,该运算符为此组合重载。然而,第二个:

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

这将引用转换V为 an object(我们很容易知道它是) - 然后分别将 from 转换为objectto Point

既然Point是 a struct,那么它就会尝试“取消装箱”相同的引用,就好像它是一个装箱的 一样Point。但它不是盒装的Point,所以错误是正确的。转换运算符不是多态的,并且仅当盒子包含适当类型的盒装副本时才允许拆箱(警告:您可以将枚举拆箱为整数,将整数拆箱为枚举 - 只要基础类型匹配)。

但是,即使Point是 a class,它仍然会失败并出现类似的错误:如果类型不兼容,保留引用的强制转换也不起作用。

于 2013-12-17T13:16:27.267 回答
3

正如其他答案所述,您不能这样做,因为您正在尝试将运行时转换应用于编译时转换操作。

如果您dynamic因为不想使用 .NET 4.0 的 DLR 而想避免,您可以使用反射自己找到转换运算符。但是,无法评论您的特定应用程序的性能:

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");
}

或者对于带有 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");
}

用法:

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

如果您希望通过隐式运算符进行转换,也可以添加“op_Implicit”。


如果要避免反射,另一种选择是预先注册转换函数、一些转换和类型查找以确定要使用的转换运算符。

需要注意的是,这里的解决方案有几个问题(线程安全、假设存在转换函数、注册冲突/重复转换函数会引发错误),因此请自行承担风险或将其用作根据您的需要进行修改的指南。

基本要点是定义一个简单的转换器来包装转换函数本身:

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);
    }
}

然后是一个可以注册这些转换的实用程序:

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);
    }
}

为了使用,首先您必须注册您希望在您的应用程序中使用的转换函数(最好在您的应用程序启动时执行注册以避免线程问题):

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

然后你的转换用法:

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

对于您的特定应用程序使用情况,不确定其中一个的性能。第一个示例更灵活一些,因为它在运行时执行检查,您不必预先注册您希望使用的转换。第二个可能会更稳定一些,因为您只注册您希望使用的转换并且没有反射,只是转换和字典查找。

于 2013-12-17T15:16:06.337 回答
2

这是因为您将其强制转换为object并且您没有明确的强制转换 - 它不会隐含地假设您希望它作为float2D.

于 2013-12-17T13:14:12.163 回答
1

对于一个真正过度设计的解决方案,将Chris Sinclair使用反射的答案与他的记忆转换方法结合起来,以保持性能,同时也不需要调用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;
    }
}

这种方法有用的原因(至少对我来说)是 LinqIEnumerable<T>.Cast<TResult>()不处理隐式或显式定义的转换运算符,只处理基类或子类的接口。我想定义自己的Cast( IEnumerable<T>.Cast<T, TResult>()) 重载,但发现它很麻烦,因为没有办法告诉编译器T可以强制转换为TResult. 鉴于上述情况,这是我的解决方案:

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));
        }
    }
}

不幸的是,C# 不允许对泛型函数进行部分推断,所以我实际上必须T在调用myEnum.Cast<MyType, MyOtherType>().

顺便说一句,由于我个人从不调用ConvertUtil.ConvertTo()我的代码,所以我将记忆化代码移动到我的扩展类中,并将代码ConvertUtil.ConvertTo<TSource, TResult>()直接放在我的IEnumerable<T>.Cast<T, TResult>()方法中。这样做的主要原因是ConvertUtil.Convert()每个项目都不会调用一次 - 每次IEnumerable<T>.Cast调用只调用一次。当然,值得注意的是通常myIEnum.Cast<MyType1, MyType2>()比 长myIEnum.Select(i => (MyType2)i),所以就是这样。

于 2018-07-12T00:01:19.440 回答