using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; namespace gaemstone.Utility; public interface ITypeWrapper { Type Type { get; } int Size { get; } bool IsUnmanaged { get; } IFieldWrapper GetFieldForAutoProperty(string propertyName); IFieldWrapper GetFieldForAutoProperty(PropertyInfo property); IFieldWrapper GetField(string fieldName); IFieldWrapper GetField(FieldInfo field); } public interface IFieldWrapper { ITypeWrapper DeclaringType { get; } FieldInfo FieldInfo { get; } PropertyInfo? PropertyInfo { get; } Func ClassGetter { get; } Action ClassSetter { get; } } public static class TypeWrapper { static readonly Dictionary _typeCache = new(); public static TypeWrapper For() => TypeWrapper.Instance; public static ITypeWrapper For(Type type) { if (!_typeCache.TryGetValue(type, out var wrapper)) _typeCache.Add(type, wrapper = (ITypeWrapper)typeof(TypeWrapper<>) .MakeGenericType(type).GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic)! .GetMethod!.Invoke(null, Array.Empty())!); return wrapper; } } public class TypeWrapper : ITypeWrapper { internal static TypeWrapper Instance { get; } = new(); readonly Dictionary _fieldCache = new(); public Type Type => typeof(TType); public int Size { get; } = Unsafe.SizeOf(); public bool IsUnmanaged { get; } = !RuntimeHelpers.IsReferenceOrContainsReferences(); TypeWrapper() { } IFieldWrapper ITypeWrapper.GetFieldForAutoProperty(string propertyName) => GetFieldForAutoProperty(propertyName); IFieldWrapper ITypeWrapper.GetFieldForAutoProperty(PropertyInfo property) => GetFieldForAutoProperty(property); IFieldWrapper ITypeWrapper.GetField(string fieldName) => GetField(fieldName); IFieldWrapper ITypeWrapper.GetField(FieldInfo field) => GetField(field); public IFieldWrapperForType GetFieldForAutoProperty(string propertyName) { var property = Type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null) throw new MissingMemberException(Type.FullName, propertyName); var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); return GetField(field, property); } public IFieldWrapperForType GetFieldForAutoProperty(PropertyInfo property) { if (property.DeclaringType != Type) throw new ArgumentException( $"Specified PropertyInfo {property} needs to be a member of type {Type}", nameof(property)); var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); return GetField(field, property); } public IFieldWrapperForType GetField(string fieldName) { var field = Type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) throw new MissingFieldException(Type.FullName, fieldName); return GetField(field, null); } public IFieldWrapperForType GetField(FieldInfo field) { if (field.DeclaringType != Type) throw new ArgumentException( $"Specified FieldInfo {field} needs to be a member of type {Type}", nameof(field)); return GetField(field, null); } public FieldWrapper GetFieldForAutoProperty(string propertyName) { var property = Type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null) throw new MissingMemberException(Type.FullName, propertyName); var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); return GetField(field, property); } public FieldWrapper GetFieldForAutoProperty(PropertyInfo property) { if (property.DeclaringType != Type) throw new ArgumentException( $"Specified PropertyInfo {property} needs to be a member of type {Type}", nameof(property)); var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); return GetField(field, property); } public FieldWrapper GetField(string fieldName) { var field = Type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) throw new MissingFieldException(Type.FullName, fieldName); return GetField(field, null); } public FieldWrapper GetField(FieldInfo field) { if (field.DeclaringType != Type) throw new ArgumentException( $"Specified FieldInfo {field} needs to be a member of type {Type}", nameof(field)); return GetField(field, null); } IFieldWrapperForType GetField(FieldInfo field, PropertyInfo? property) { if (_fieldCache.TryGetValue(field, out var cached)) return cached; var type = typeof(FieldWrapper<>).MakeGenericType(typeof(TType), field.FieldType); var wrapper = (IFieldWrapperForType)Activator.CreateInstance( type, BindingFlags.Instance | BindingFlags.NonPublic, null, new object?[] { this, field, property }, null)!; _fieldCache.Add(field, wrapper); return wrapper; } FieldWrapper GetField(FieldInfo field, PropertyInfo? property) { if (_fieldCache.TryGetValue(field, out var cached)) return (FieldWrapper)cached; if (field.FieldType != typeof(TField)) throw new ArgumentException( $"FieldType ({field.FieldType}) does not match TField ({typeof(TField)})", nameof(TField)); var wrapper = new FieldWrapper(this, field, property); _fieldCache.Add(field, wrapper); return wrapper; } public interface IFieldWrapperForType : IFieldWrapper { delegate object? ValueGetterAction(in TType obj); delegate void ValueSetterAction(ref TType obj, object? value); Func IFieldWrapper.ClassGetter => (obj) => ClassGetter((TType)obj); Action IFieldWrapper.ClassSetter => (obj, value) => ClassSetter((TType)obj, value); new Func ClassGetter { get; } new Action ClassSetter { get; } ValueGetterAction ByRefGetter { get; } ValueSetterAction ByRefSetter { get; } } public class FieldWrapper : IFieldWrapperForType { public delegate TField ValueGetterAction(in TType obj); public delegate void ValueSetterAction(ref TType obj, TField value); Func? _classGetter; Action? _classSetter; ValueGetterAction? _byRefGetter; ValueSetterAction? _byRefSetter; public ITypeWrapper DeclaringType { get; } public FieldInfo FieldInfo { get; } public PropertyInfo? PropertyInfo { get; } internal FieldWrapper(ITypeWrapper type, FieldInfo field, PropertyInfo? property) { DeclaringType = type; FieldInfo = field; PropertyInfo = property; } Func IFieldWrapperForType.ClassGetter => (obj) => ClassGetter(obj); Action IFieldWrapperForType.ClassSetter => (obj, value) => ClassSetter(obj, (TField)value!); public Func ClassGetter => _classGetter ??= BuildGetter>(false); public Action ClassSetter => _classSetter ??= BuildSetter>(false); IFieldWrapperForType.ValueGetterAction IFieldWrapperForType.ByRefGetter => (in TType obj) => ByRefGetter(in obj); IFieldWrapperForType.ValueSetterAction IFieldWrapperForType.ByRefSetter => (ref TType obj, object? value) => ByRefSetter(ref obj, (TField)value!); public ValueGetterAction ByRefGetter => _byRefGetter ??= BuildGetter(true); public ValueSetterAction ByRefSetter => _byRefSetter ??= BuildSetter(true); TDelegate BuildGetter(bool byRef) where TDelegate : Delegate { if (DeclaringType.Type.IsValueType && !byRef) throw new InvalidOperationException( $"Can't build getter for value type ({DeclaringType.Type}) without using ref"); var method = new DynamicMethod( $"Get_{DeclaringType.Type.Name}_{FieldInfo.Name}{(byRef ? "_ByRef" : "")}", typeof(TField), new[] { byRef ? typeof(TType).MakeByRefType() : typeof(TType) }, typeof(TType).Module, true); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); if (byRef && !DeclaringType.Type.IsValueType) il.Emit(OpCodes.Ldind_Ref); il.Emit(OpCodes.Ldfld, FieldInfo); il.Emit(OpCodes.Ret); return method.CreateDelegate(); } TDelegate BuildSetter(bool byRef) where TDelegate : Delegate { if (DeclaringType.Type.IsValueType && !byRef) throw new InvalidOperationException( $"Can't build setter for value type ({DeclaringType.Type}) without using ref"); var method = new DynamicMethod( $"Set_{DeclaringType.Type.Name}_{FieldInfo.Name}{(byRef ? "_ByRef" : "")}", null, new[] { byRef ? typeof(TType).MakeByRefType() : typeof(TType), typeof(TField) }, typeof(TType).Module, true); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); if (byRef && !DeclaringType.Type.IsValueType) il.Emit(OpCodes.Ldind_Ref); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, FieldInfo); il.Emit(OpCodes.Ret); return method.CreateDelegate(); } } }