namespace Unosquare.Swan
{
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Attributes;
///
/// Provides various extension methods for value types and structs.
///
public static class ValueTypeExtensions
{
///
/// Clamps the specified value between the minimum and the maximum.
///
/// The type of value to clamp.
/// The value.
/// The minimum.
/// The maximum.
/// A value that indicates the relative order of the objects being compared.
public static T Clamp(this T value, T min, T max)
where T : struct, IComparable
{
if (value.CompareTo(min) < 0) return min;
return value.CompareTo(max) > 0 ? max : value;
}
///
/// Clamps the specified value between the minimum and the maximum.
///
/// The value.
/// The minimum.
/// The maximum.
/// A value that indicates the relative order of the objects being compared.
public static int Clamp(this int value, int min, int max)
=> value < min ? min : (value > max ? max : value);
///
/// Determines whether the specified value is between a minimum and a maximum value.
///
/// The type of value to check.
/// The value.
/// The minimum.
/// The maximum.
///
/// true if the specified minimum is between; otherwise, false.
///
public static bool IsBetween(this T value, T min, T max)
where T : struct, IComparable
{
return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0;
}
///
/// Converts an array of bytes into the given struct type.
///
/// The type of structure to convert.
/// The data.
/// a struct type derived from convert an array of bytes ref=ToStruct".
public static T ToStruct(this byte[] data)
where T : struct
{
return ToStruct(data, 0, data.Length);
}
///
/// Converts an array of bytes into the given struct type.
///
/// The type of structure to convert.
/// The data.
/// The offset.
/// The length.
///
/// A managed object containing the data pointed to by the ptr parameter.
///
/// data.
public static T ToStruct(this byte[] data, int offset, int length)
where T : struct
{
if (data == null)
throw new ArgumentNullException(nameof(data));
var buffer = new byte[length];
Array.Copy(data, offset, buffer, 0, buffer.Length);
var handle = GCHandle.Alloc(GetStructBytes(buffer), GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure(handle.AddrOfPinnedObject());
}
finally
{
handle.Free();
}
}
///
/// Converts a struct to an array of bytes.
///
/// The type of structure to convert.
/// The object.
/// A byte array containing the results of encoding the specified set of characters.
public static byte[] ToBytes(this T obj)
where T : struct
{
var data = new byte[Marshal.SizeOf(obj)];
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
return GetStructBytes(data);
}
finally
{
handle.Free();
}
}
///
/// Swaps the endianness of an unsigned long to an unsigned integer.
///
/// The bytes contained in a long.
///
/// A 32-bit unsigned integer equivalent to the ulong
/// contained in longBytes.
///
public static uint SwapEndianness(this ulong longBytes)
=> (uint) (((longBytes & 0x000000ff) << 24) +
((longBytes & 0x0000ff00) << 8) +
((longBytes & 0x00ff0000) >> 8) +
((longBytes & 0xff000000) >> 24));
private static byte[] GetStructBytes(byte[] data)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
#if !NETSTANDARD1_3
var fields = typeof(T).GetTypeInfo()
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
#else
var fields = typeof(T).GetTypeInfo().DeclaredFields;
#endif
var endian = Runtime.AttributeCache.RetrieveOne();
foreach (var field in fields)
{
if (endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false))
continue;
var offset = Marshal.OffsetOf(field.Name).ToInt32();
var length = Marshal.SizeOf(field.FieldType);
endian = endian ?? Runtime.AttributeCache.RetrieveOne(field);
if (endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian ||
endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian))
{
Array.Reverse(data, offset, length);
}
}
return data;
}
}
}