You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

229 lines
9.5 KiB

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<object, object?> ClassGetter { get; }
Action<object, object?> ClassSetter { get; }
}
public static class TypeWrapper
{
static readonly Dictionary<Type, ITypeWrapper> _typeCache = new();
public static TypeWrapper<T> For<T>()
=> TypeWrapper<T>.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<object>())!);
return wrapper;
}
}
public class TypeWrapper<TType> : ITypeWrapper
{
internal static TypeWrapper<TType> Instance { get; } = new();
readonly Dictionary<FieldInfo, IFieldWrapperForType> _fieldCache = new();
public Type Type => typeof(TType);
public int Size { get; } = Unsafe.SizeOf<TType>();
public bool IsUnmanaged { get; } = !RuntimeHelpers.IsReferenceOrContainsReferences<TType>();
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<TField> GetFieldForAutoProperty<TField>(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<TField>(field, property);
}
public FieldWrapper<TField> GetFieldForAutoProperty<TField>(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<TField>(field, property);
}
public FieldWrapper<TField> GetField<TField>(string fieldName)
{
var field = Type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field == null) throw new MissingFieldException(Type.FullName, fieldName);
return GetField<TField>(field, null);
}
public FieldWrapper<TField> GetField<TField>(FieldInfo field)
{
if (field.DeclaringType != Type) throw new ArgumentException(
$"Specified FieldInfo {field} needs to be a member of type {Type}", nameof(field));
return GetField<TField>(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<TField> GetField<TField>(FieldInfo field, PropertyInfo? property)
{
if (_fieldCache.TryGetValue(field, out var cached)) return (FieldWrapper<TField>)cached;
if (field.FieldType != typeof(TField)) throw new ArgumentException(
$"FieldType ({field.FieldType}) does not match TField ({typeof(TField)})", nameof(TField));
var wrapper = new FieldWrapper<TField>(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<object, object?> IFieldWrapper.ClassGetter => (obj) => ClassGetter((TType)obj);
Action<object, object?> IFieldWrapper.ClassSetter => (obj, value) => ClassSetter((TType)obj, value);
new Func<TType, object?> ClassGetter { get; }
new Action<TType, object?> ClassSetter { get; }
ValueGetterAction ByRefGetter { get; }
ValueSetterAction ByRefSetter { get; }
}
public class FieldWrapper<TField> : IFieldWrapperForType
{
public delegate TField ValueGetterAction(in TType obj);
public delegate void ValueSetterAction(ref TType obj, TField value);
Func<TType, TField>? _classGetter;
Action<TType, TField>? _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<TType, object?> IFieldWrapperForType.ClassGetter => (obj) => ClassGetter(obj);
Action<TType, object?> IFieldWrapperForType.ClassSetter => (obj, value) => ClassSetter(obj, (TField)value!);
public Func<TType, TField> ClassGetter => _classGetter ??= BuildGetter<Func<TType, TField>>(false);
public Action<TType, TField> ClassSetter => _classSetter ??= BuildSetter<Action<TType, TField>>(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<ValueGetterAction>(true);
public ValueSetterAction ByRefSetter => _byRefSetter ??= BuildSetter<ValueSetterAction>(true);
TDelegate BuildGetter<TDelegate>(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>();
}
TDelegate BuildSetter<TDelegate>(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<TDelegate>();
}
}
}