二进制文字的转换
3.80/5 (6投票s)
To And From Binary Literals for all standard .net numeric value types.
引言
最近,我阅读了一篇关于解释二进制字面量的 CodeProject 文章。该文章仅限于将二进制字面量解释为值类型,但并未进行反向操作。在调试过程中,我经常想快速检查传入的具有字节掩码的值是否具有某个特定位。因此,我想创建一个可以双向转换的工具。这个想法我已有多年,但你知道的,就是一直没开始。感谢这篇文章,我终于找到了意志/时间来做这件事。其结果是一个小型框架,允许以下类型进行相互转换:byte、sbyte、ushort、short、uint、int、ulong、long、float、double、decimal 和 BigInteger。
背景
代码利用了 .NET 的十六进制代码生成功能。十六进制代码被转换为二进制字面量。对于浮点变量,我使用 System.BitConvertor 或 BigInteger 的一些小技巧将值转换为整数值。从二进制字面量重新创建值也是如此。
我希望以一种可转换的方式使用该框架,这将在“使用代码”部分进行描述。 Equals 和 GetHashCode 已实现,尽管我强烈建议不要在此框架的基础上构建健壮的代码,与使用普通值类型相比,转换会花费大量时间。
代码可能会引发许多不同的错误,所有从值类型到值类型以及值类型到值的转换都会像正常的类型转换一样引发错误。如果正常情况下不允许,那么现在也不允许。
除了所有预期的错误之外,还可能引发其他错误:如果提供的参数为 null 且函数不允许返回 null,则会发生 Null 异常。如果提供的字面量大于指定值类型所能处理的字节数,或者指定了不受支持的类型,则会发生 Argument Exception。
我使代码与 c# 4.0 兼容,因此类文件本身可以从 Visual Studio 2010 及更高版本中重用。我用 Visual studio 2015 制作了示例。
由于我决定也支持 BigInteger,这意味着您需要引用 System.Numerics 框架才能使用这个小型框架。
代码工作原理
首先,我创建了一个小型对象扩展器来处理 null 测试。
internal static class ObjectExtender
{
internal static bool IsInitialized(this object ToTest)
{
return !object.ReferenceEquals(ToTest, null);
}
internal static bool IsNull(this object ToTest)
{
return object.ReferenceEquals(ToTest, null);
}
}
我创建了这个扩展器以便更容易地检查 null。我通常在我的代码的程序集中使用它。您可能会问为什么。如果您检查 null 并且您检查的类/结构实现了自己的相等性,则会触发其运算符。所以如果是一个写得不好的测试,不考虑 null,耗时很长,等等,它会因为一个 null 检查而影响执行。C# 这样做具体原因我也不清楚,他们应该有充分的理由,但就我个人而言,我希望看到运算符在 null 比较时不会被触发。
主构造函数
然后我创建了主/第一个构造函数
public Binarie(string value, Type valueType = default(Type))
{
if (ConfirmValidLiteralString(value))
{
this._value = Format(value);
string Temp = _value.Replace(" ", "");
int Length = Temp.Length / 8;
if (((decimal)Temp.Length) / ((decimal)8) > Length)
{
Length++;
}
if (valueType.IsNull())
{
//must be user call detect closest type.
if (Length == 1)
{
_TypeOfValue = typeof(byte);
}
else if (Length == 2)
{
_TypeOfValue = typeof(ushort);
}
else if (Length <= 4)
{
_TypeOfValue = typeof(uint);
}
else if (Length <= 8)
{
_TypeOfValue = typeof(ulong);
}
else
{
_TypeOfValue = typeof(BigInteger);
}
}
else
{
int Needed = SizeOfType(valueType);
if (Needed == 0)
{
throw new ArithmeticException("Type not supported");
}
else if ((Length <= Needed) || (Needed == -1))
{
this._TypeOfValue = valueType;
}
else
{
throw new ArgumentException("To many bits supplied for specified valuetype");
}
}
}
else if (valueType.IsInitialized() && (SizeOfType(valueType) != 0))
{
_value = "0";
this._TypeOfValue = valueType;
}
else
{
throw new ArgumentException("Illegal value specified.");
}
if (_TypeOfValue == typeof(byte))
{
_ValueCache = unchecked((byte)ConvertUnsigned(byte.MinValue, byte.MaxValue));
}
else if (_TypeOfValue == typeof(sbyte))
{
_ValueCache = unchecked((sbyte)ConvertUnsigned(sbyte.MinValue, sbyte.MaxValue));
}
else if (_TypeOfValue == typeof(ushort))
{
_ValueCache = unchecked((ushort)ConvertUnsigned(ushort.MinValue, ushort.MaxValue));
}
else if (_TypeOfValue == typeof(short))
{
_ValueCache = unchecked((short)ConvertUnsigned(short.MinValue, short.MaxValue));
}
else if (_TypeOfValue == typeof(uint))
{
_ValueCache = unchecked((uint)ConvertUnsigned(uint.MinValue, uint.MaxValue));
}
else if (_TypeOfValue == typeof(int))
{
_ValueCache = unchecked((int)ConvertUnsigned(int.MinValue, int.MaxValue));
}
else if (_TypeOfValue == typeof(ulong))
{
_ValueCache = unchecked((ulong)ConvertUnsigned(ulong.MinValue, ulong.MaxValue));
}
else if (_TypeOfValue == typeof(long))
{
_ValueCache = unchecked((long)ConvertUnsigned(long.MinValue, long.MaxValue));
}
else if (_TypeOfValue == typeof(float))
{
_ValueCache = BitConverter.ToSingle(BitConverter.GetBytes(unchecked((int)ConvertUnsigned(int.MinValue, int.MaxValue))), 0);
}
else if (_TypeOfValue == typeof(double))
{
_ValueCache = BitConverter.Int64BitsToDouble(unchecked((long)ConvertUnsigned(long.MinValue, long.MaxValue)));
}
else if (_TypeOfValue == typeof(decimal))
{
BigInteger Temp;
if (BigInteger.TryParse(ConvertBitsToHex(_value),
System.Globalization.NumberStyles.HexNumber | System.Globalization.NumberStyles.AllowHexSpecifier,
System.Globalization.CultureInfo.CurrentCulture.NumberFormat, out Temp))
{
_ValueCache = (decimal)Temp;
}
else
{
throw new ArgumentException("Value cannot be converted to decimal.");
}
}
else if (_TypeOfValue == typeof(BigInteger))
{
BigInteger Temp;
if (BigInteger.TryParse(ConvertBitsToHex(_value),
System.Globalization.NumberStyles.HexNumber | System.Globalization.NumberStyles.AllowHexSpecifier,
System.Globalization.CultureInfo.CurrentCulture.NumberFormat, out Temp))
{
_ValueCache = Temp;
}
else
{
throw new ArgumentException("Value cannot be converted to BigInteger.");
}
}
else
{
throw new System.NotSupportedException("Only the standard numeric valuetypes are supported.");
}
SetHash();
}
正如您所见,它非常庞大。
首先,它检查提供的字符串是否只包含 0、1 和空格。如果字符串包含其他内容,则会引发错误。如果字符串不包含 0 或 1,则将其视为空。如果字符串为空且已指定类型,并且指定的类型是受支持的类型,则 Binarie 将使用二进制字面量 0 字符串进行初始化。如果字符串为空但未指定类型,则会引发错误。
在此步骤之后,字符串将被格式化为 4 位 1 个空格,并保存在缓存变量 _value 中。然后,格式化后的字符串将去除所有空格,并计算字节长度。如果现在已提供受支持的类型,则会检查提供的位数是否适合指定类型所占的字节数,如果不适合,则会引发错误。如果未提供类型,它将选择最合适的单元进行操作并将其选为类型。
当 _value 和 _ TypeOfValue 确定后,_valueCache 将填充指定类型的真实实例。因此,当使用此原始构造函数时,放入 _valueCache 的值是从二进制字面量重新创建的。
我省略了一些特殊的转换来实现上述功能
_ValueCache = BitConverter.ToSingle(BitConverter.GetBytes(unchecked((int)ConvertUnsigned(int.MinValue, int.MaxValue))), 0);
ConverUnsigned 用于所有小于或等于 ulong 的值。此函数从位创建 ulong。然后它检查结果值是否对应于提供的范围。返回结果,接收者对其进行 unchecked cast 到所需的值类型。
在上述情况下,这意味着一个 int,而结果是一个 float。System.Bitconverter 具有获取值类型的字节以及从字节创建 float 的可能性。我利用这个技巧来在 int 和 float 之间进行转换,反之亦然。由于这一步,我可以使用标准的 int.ToString("X8") 来生成十六进制代码。
_ValueCache = BitConverter.Int64BitsToDouble(unchecked((long)ConvertUnsigned(long.MinValue, long.MaxValue)));
对于 double,这更容易,System.BitConvertor 提供了直接函数。
然后我想支持 decimal,但 System.Bitconvertor 无法帮助我。事实证明 System.Numbers.BigInteger 可以从 decimal 创建并转换为 decimal。BigInteger 支持 ToString("X")(一种十六进制方法)。其次,BigInteger 可以解析十六进制字符串为一个值。我找到了答案。
BigInteger Temp;
if (BigInteger.TryParse(ConvertBitsToHex(_value),
System.Globalization.NumberStyles.HexNumber | System.Globalization.NumberStyles.AllowHexSpecifier,
System.Globalization.CultureInfo.CurrentCulture.NumberFormat, out Temp))
{
_ValueCache = (decimal)Temp;
}
else
{
throw new ArgumentException("Value cannot be converted to decimal.");
}
该二进制字面量通过 ConvertBitsToHex 函数转换为十六进制代码,然后 BigInteger 尝试解析该字符串,成功后我们可以将 BigInteger 转换为 decimal(如果其值在其范围内,这应该是,因为我们是从 decimal 创建的)。如果无法转换,将引发错误。来自二进制字面量的 BigInteger 的工作方式相同,但没有 decimal 转换。
内部构造函数
为了直接支持值(byte、sbyte、ushort、short、uint、int、ulong、long、float、double、decimal、BigInteger)到 Binarie,我创建了一个内部构造函数,它将跳过上述所有步骤,以节省时间。
internal Binarie(string value, Type ValueType, object RealValue)
{
this._value = value;
this._TypeOfValue = ValueType;
this._ValueCache = RealValue;
SetHash();
}
然后我为每个特定的支持值类型创建了一个运算符。
public static implicit operator Binarie(byte ToConvert)
{
return new Binarie(ConvertHexToBits(ToConvert.ToString("X2")), typeof(byte), ToConvert);
}
首先,入站值将使用正常的 .net 功能转换为十六进制。然后将此十六进制值转换为二进制字面量。使用结果字符串、类型和原始值调用构造函数。因此,在此创建中,字符串不会被重新解释回 _valueCache,也不会进行任何检查,从而节省了时间。
哈希
两个构造函数都在最后填充哈希变量。此哈希包括对二进制字面量和类型名称的组合字符串进行 GetHashCode。这应该会带来与字符串哈希一样强的哈希。
转换回请求的值类型
通过为每个支持的值类型实现运算符,支持转换回请求的值类型。
public static implicit operator byte(Binarie ToConvert)
{
byte Result = 0;
if (ToConvert.IsNull())
{
throw new NullReferenceException("ToConvert cannot be null.");
}
if (ToConvert._TypeOfValue == typeof(decimal))
{
Result = (byte)(decimal)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(BigInteger))
{
Result = (byte)(BigInteger)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(float))
{
Result = (byte)(float)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(double))
{
Result = (byte)(double)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(sbyte))
{
Result = (byte)(sbyte)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(short))
{
Result = (byte)(short)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(int))
{
Result = (byte)(int)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(long))
{
Result = (byte)(long)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(ushort))
{
Result = (byte)(ushort)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(uint))
{
Result = (byte)(uint)(ToConvert._ValueCache);
}
else if (ToConvert._TypeOfValue == typeof(ulong))
{
Result = (byte)(ulong)(ToConvert._ValueCache);
}
else
{
Result = (byte)(ToConvert._ValueCache);
}
return Result;
}
首先,将检查传入的 Binarie 是否不为 null,否则将引发错误。
其次,将确定 Binarie 值的类型,并通过其原始类型到请求类型的转换。然后将结果转换为指定的需要,并返回。
使用值类型说明符支持转换
我希望支持类型说明的 Binaries,首先我考虑了继承,但这会带来性能损失。无法从 Binarie 转换为其对应的 Binarie<>。因此,我决定委托功能给一个新的泛型类。我使用 where TType:struct 限制了允许的类型,以便所有类变量至少会引发一个错误。
public class Binarie<TType> : IBinarie
where TType:struct
{
private static readonly Type OurType = typeof(TType);
private Binarie Parent = null;
private int Hash = 0;
private Binarie(string Value, object RealValue)
{
Parent = new Binarie(Value, OurType, RealValue);
SetHash();
}
public Binarie(string Value)// : base(Value, typeof(TType))
{
if ((OurType == typeof(sbyte)) ||
(OurType == typeof(byte)) ||
(OurType == typeof(short)) ||
(OurType == typeof(ushort)) ||
(OurType == typeof(int)) ||
(OurType == typeof(uint)) ||
(OurType == typeof(long)) ||
(OurType == typeof(ulong)) ||
(OurType == typeof(float)) ||
(OurType == typeof(double)) ||
(OurType == typeof(decimal)) ||
(OurType == typeof(BigInteger)))
{
Parent = new Binarie(Value, OurType);
}
else
{
throw new System.NotSupportedException("Only the standard numeric valuetypes are supported.");
}
SetHash();
}
...
operators, from to written out per value type
...
}
对于每个 TType,有一个静态只读变量 OurType,它指定我们是什么类型。其次,有一个 Binarie 的 Parent 实例,它执行所有实际工作。当调用构造函数时,将检查指定的类型,如果不支持,则会引发错误。否则,将使用指定的二进制字面量字符串和 OurType 初始化 Binarie。
出于与 Binarie 的内部构造函数相同的原因,此类具有一个私有构造函数,该构造函数通过内部构造函数构造一个 Binarie。
哈希
两个构造函数都在末尾计算哈希,这也是一个 GetHashCode,它基于二进制字面量字符串和类型名称构建的字符串,但为了与 Binarie 区分开,还添加了 TType 后缀。
这应该足以确保 Binarie<TType> 与具有相同值的特定类型 Binarie 不同。
清晰度
我在源代码中使用了大量的重载函数,这是为了在高层给人一种使用 1 个函数的感觉。这应该会给代码带来一些清晰度。我还添加了许多区域来隔离特定类型的功能。
使用代码
如您所读,有几种方法可以构造 Binarie 或 Binarie<TType>
方法一
Binarie Value = new Binarie("1111 1111");
此调用将计算提供的位数并将确定最合适的整数,位数将被解释
为无符号。可能的返回值类型为:byte、ushort、uint、ulong、BigInteger。
方法二
Binarie Value = (Binarie)"1111 1111";
方法二与方法一完全相同。
方法三
Binarie Value = new Binarie("1111 1111", typeof(sbyte));
此调用会将提供的值转换为提供的值类型,如果字面量过大,函数将引发错误。
方法四
Binarie Value = (Binarie<sbyte>)"1111 1111";
这里实际上发生了两件事:首先将创建一个 Binarie<sbyte>,其次将此对象转换为 Binarie。适用的规则与方法三相同。
可交换性
我希望能够与 Binarie 相互转换,以及在 Binarie<valuetype> 和 Binarie<othervaluetype> 之间进行转换。
通过大量手工编写的 if 语句、转换和其他一些小技巧,这已经成为可能。如何实现可以在代码中阅读。
实际上,这意味着您可以编写
byte Value = (Binarie<int>)"1111";
或者更极端的
byte ByteTest = (Binarie)(Binarie<float>)(Binarie<byte>)(string)(Binarie<byte>)(Binarie)"1111 1111 1111 1111";
这里上面发生的事情
首先我们创建一个 ushort 类型的 Binarie,它被转换为 Binarie<byte>,从中我们得到二进制字面量字符串,然后我们用它来创建另一个 Binarie<byte>,它又被转换为 Binarie<float>。最后一个被转换为普通的 Binarie,最后被转换为 byte。
我希望这个极端的例子能展示可以做什么。
挑战
在我开发上述代码时,我遇到了一项挑战,即基类和派生类之间的逻辑限制。
您不能编写从基类到派生类的运算符。虽然我仍然想要这种可能性,但至少它应该给人的感觉是可以做到。通过让 Binarie 的泛型版本在 Binarie 本身中执行所有实际工作,并将其保留为内部变量,这使我们能够轻松地“转换”到我们的基类形式。反之则需要更多工作,如果底层值类型不同,则需要进行转换和二进制字面量的重新计算,而它确实会这样做。
为了仍然能够将 Binarie 和 Binarie<TType> 在同一个数组中一起使用而无需额外的转换,我在两者上都实现了一个接口。
public interface IBinarie
{
string ToString();
IBinarie FromString(string value);
Type TypeOfValue { get; }
object Value { get; }
}
ToString 结果是二进制字面量字符串。
FromString 将创建一个新的 Binarie 或 Binarie<TType>。提供的二进制字面量字符串必须与 TypeOfValue 的字节数一致,如果位数过高,则会引发错误。
TypeOfValue 提供类型。
Value 提供我们缓存的真实值,Binarie 代表该值。Binarie<TType> 上的 Value 已显式实现,并添加了 TType Value { get; } 以便更容易地处理它。
总而言之,这应该足以多态地处理 Binarie 和任何 Binarie<TType> 实例。
关注点
在制作这个小型框架的过程中,我学会了如何将浮点值类型转换为整数值类型并再次转换回来,在此项目之前我从未需要过。
历史
第二次发布:添加了完整的“代码工作原理”部分,并修改了一些文本。
第一次发布。
附注
代码作为由 Windows 窗体使用的程序集提供,如果您进入 forn_load,可以调试一些示例。
