讓我先狀態,有沒有「官方」的解決方案。通常我不會依賴內部實現,所以我提供以下內容,僅僅是因爲你說這對你解決問題非常重要。
如果你看一看參考源,你會看到,所有的解析方法是在(不幸的是內部)System.Number類實現。進一步調查中,decimal
相關的方法是TryParseDecimal和ParseDecimal,他們都使用這樣的
byte* buffer = stackalloc byte[NumberBuffer.NumberBufferBytes];
var number = new NumberBuffer(buffer);
if (TryStringToNumber(s, styles, ref number, numfmt, true))
{
// other stuff
}
其中NumberBuffer
是另一個內部struct
。關鍵是整個解析發生在TryStringToNumber
方法中,並且結果用於產生結果。我們感興趣的是一個名爲precision
的NumberBuffer字段,它由上述方法填充。
與所有考慮到這一點,我們可以生成一個類似的方法只是調用基小數方法,以保證正常的驗證之後提取的精度/我們做我們的後期處理異常之前。因此,該方法會是這樣
static unsafe bool GetPrecision(string s, NumberStyles style, NumberFormatInfo numfmt)
{
byte* buffer = stackalloc byte[Number.NumberBuffer.NumberBufferBytes];
var number = new NumberBuffer(buffer);
TryStringToNumber(s, styles, ref number, numfmt, true);
return number.precision;
}
但請記住,這些類型是內部的,以及他們的方法,因此很難適用正常反射,委託或Expression
基礎的技術。幸運的是,使用System.Reflection.Emit
編寫這種方法並不難。全面實施如下
public static class DecimalUtils
{
public static decimal ParseExact(string s, NumberStyles style = NumberStyles.Number, IFormatProvider provider = null)
{
// NOTE: Always call base method first
var value = decimal.Parse(s, style, provider);
if (!IsValidPrecision(s, style, provider))
throw new InvalidCastException(); // TODO: throw appropriate exception
return value;
}
public static bool TryParseExact(string s, out decimal result, NumberStyles style = NumberStyles.Number, IFormatProvider provider = null)
{
// NOTE: Always call base method first
return decimal.TryParse(s, style, provider, out result) && !IsValidPrecision(s, style, provider);
}
static bool IsValidPrecision(string s, NumberStyles style, IFormatProvider provider)
{
var precision = GetPrecision(s, style, NumberFormatInfo.GetInstance(provider));
return precision <= 29;
}
static readonly Func<string, NumberStyles, NumberFormatInfo, int> GetPrecision = BuildGetPrecisionFunc();
static Func<string, NumberStyles, NumberFormatInfo, int> BuildGetPrecisionFunc()
{
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic;
const BindingFlags InstanceFlags = Flags | BindingFlags.Instance;
const BindingFlags StaticFlags = Flags | BindingFlags.Static;
var numberType = typeof(decimal).Assembly.GetType("System.Number");
var numberBufferType = numberType.GetNestedType("NumberBuffer", Flags);
var method = new DynamicMethod("GetPrecision", typeof(int),
new[] { typeof(string), typeof(NumberStyles), typeof(NumberFormatInfo) },
typeof(DecimalUtils), true);
var body = method.GetILGenerator();
// byte* buffer = stackalloc byte[Number.NumberBuffer.NumberBufferBytes];
var buffer = body.DeclareLocal(typeof(byte*));
body.Emit(OpCodes.Ldsfld, numberBufferType.GetField("NumberBufferBytes", StaticFlags));
body.Emit(OpCodes.Localloc);
body.Emit(OpCodes.Stloc, buffer.LocalIndex);
// var number = new Number.NumberBuffer(buffer);
var number = body.DeclareLocal(numberBufferType);
body.Emit(OpCodes.Ldloca_S, number.LocalIndex);
body.Emit(OpCodes.Ldloc, buffer.LocalIndex);
body.Emit(OpCodes.Call, numberBufferType.GetConstructor(InstanceFlags, null,
new[] { typeof(byte*) }, null));
// Number.TryStringToNumber(value, options, ref number, numfmt, true);
body.Emit(OpCodes.Ldarg_0);
body.Emit(OpCodes.Ldarg_1);
body.Emit(OpCodes.Ldloca_S, number.LocalIndex);
body.Emit(OpCodes.Ldarg_2);
body.Emit(OpCodes.Ldc_I4_1);
body.Emit(OpCodes.Call, numberType.GetMethod("TryStringToNumber", StaticFlags, null,
new[] { typeof(string), typeof(NumberStyles), numberBufferType.MakeByRefType(), typeof(NumberFormatInfo), typeof(bool) }, null));
body.Emit(OpCodes.Pop);
// return number.precision;
body.Emit(OpCodes.Ldloca_S, number.LocalIndex);
body.Emit(OpCodes.Ldfld, numberBufferType.GetField("precision", InstanceFlags));
body.Emit(OpCodes.Ret);
return (Func<string, NumberStyles, NumberFormatInfo, int>)method.CreateDelegate(typeof(Func<string, NumberStyles, NumberFormatInfo, int>));
}
}
用它在你自己的風險:)
你可以嘗試十進制轉換爲字符串,並與原來的比較。 – IllidanS4
@ IllidanS4:但是它會拒絕所有未格式化的輸入,這些格式根據我用來將十進制值格式化爲字符串的格式。 –
是的。如果小於28或29,則可以犧牲一位十進制數並計算轉換後的字符串中的位數。如果是,則某些數字可能已丟失,儘管它可能拒絕具有29位有效數字的輸入。 – IllidanS4