通用函数

时间:2018-02-08 17:41:34

标签: c# generics

我试图编写一个通用方法来执行从long到其他类型的未经检查的赋值。这是简化版本:

    private static void AssignHex<T>(string hex, out T val) where T : struct 
    {
        if (long.TryParse(hex, NumberStyles.AllowHexSpecifier, null, out long lval))
        {
            unchecked
            {
                val = (T)Convert.ChangeType(lval, typeof(T));
            }
        }
        else
        {
            val = default(T);
        }
    }

除了输入字符串&#34; FFFFFFFF&#34;并输入int,我希望得到-1,而不是溢出异常。等效的非泛型方法可以正常工作:

    private static void AssignHex2(string hex, out int val)
    {
        if (long.TryParse(hex, NumberStyles.AllowHexSpecifier, null, out long lval))
        {
            unchecked
            {
                val = (int)lval;
            }
        }
        else
        {
            val = default(int);
        }
    }

我可以简单地编写非泛型,但令我困扰的是我无法使用通用版本。任何解决方案?

2 个答案:

答案 0 :(得分:4)

尽管System.Convert.ChangeType非常方便很多次,但你不能在你的场景中使用它。十进制的FFFFFFFF是4294967295,无法在int类型中表示,转换失败。抛出的特定代码是this(如果long长于最大int值则检查。)

如果您需要此功能,则必须手动编写与System.Convert.ChangeType - if语句中类似的代码,其中包含unchecked块中包含的适当强制转换的不同可能类型。或者根本不使用泛型,并且对您感兴趣的每种类型都有重载。

编辑:最好完全删除unchecked块并将十六进制值直接解析为适当的类型,而不是先将其解析为long。这样,您将直接将其解析为期望值,并在值超出范围时收到错误,而不是仅使用unchecked块静默忽略剩余位。

EDIT2:使用two's complement表示负数。简而言之,您可以通过翻转代表正数并添加1的所有位来从正数得到负数。这意味着负数的二进制或十六进制表示取决于为数字分配的位数。因此对于8位数(sbyte)-1是0xFF,对于16位数(short)是0xFFFF,对于32位数(int)是0xFFFFFFFF和64位数( long)是0xFFFFFFFFFFFFFFFF。由于您将十六进制值0xFFFFFFFF解析为long,因此您不是-1,因为您实际上正在解析0x0000000FFFFFFFF。你的unchecked块的作用是,当转换为较低的精度数时,它将只需要较低精度类型所需的位数,并在不进行任何检查的情况下丢弃其余部分。想象一下,你有0XF0000000FFFFFFFF。如果你解析这个,你得到~1 quintillion但是未经检查的强制转换为int你会得到-1,完全忽略最重要的4位。

答案 1 :(得分:0)

确定。涉及到一些痛苦,但答案是:

    private static bool AssignHex4<T>(string hex, out T val)
    {
        Type t = typeof(T);
        MethodInfo mi = t.GetMethod("TryParse", new Type[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(T).MakeByRefType()});
        if (mi != null)
        {
            object[] parameters = new object[] {hex, NumberStyles.AllowHexSpecifier, null, null};
            object result = mi.Invoke(null, parameters);
            if ((bool) result)
            {
                val = (T)parameters[3];
                return true;
            }
        }

        val = default(T);
        return false;
    }

一些SO答案需要大声宣传,这些答案有助于将它们结合在一起:

MethodInfo.Invoke with out Parameter

Specifying out params for Type.GetMethod

我承认这不是一个特别优雅的答案,但我认为它是全面的,而这正是我所寻找的。

考虑...

        AssignHex4("FF", out byte r1);
        AssignHex4("FFFF", out short r2);
        AssignHex4("FFFFFFFF", out int r3);
        AssignHex4("FFFFFFFFFFFFFFFF", out long r4);
        AssignHex4("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", out BigInteger r5);
        Console.WriteLine("Convert in reflection function returns:" + r1 + ", " + r2 + ", " + r3 + ", " + r4 + ", " + r5);

导致......

Convert in reflection function returns:255, -1, -1, -1, -1