RaspberryIO/Unosquare.Swan.Lite/Extensions.ValueTypes.cs
2019-12-04 17:10:06 +01:00

147 lines
5.8 KiB
C#

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