Editor/TypeExtensions.cs
using System;
using System.Linq;
using System.Reflection;
namespace ExtendedEditor;
public static class TypeExtensions
{
private static readonly Type _unmanagedGenericType = typeof(UnmanagedGeneric<>);
private class UnmanagedGeneric<T> where T : unmanaged { }
public static ConstructorInfo? GetEffectivelyDefaultConstructor(this Type type, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance)
{
var result = type.GetConstructor(bindingFlags, Type.EmptyTypes);
if(result is not null)
return result;
var constructors = type.GetConstructors(bindingFlags);
foreach(var constructor in constructors)
{
var parameters = constructor.GetParameters();
if(parameters.All(p => p.HasDefaultValue))
return constructor;
}
return null;
}
public static object? CreateByEffectivelyDefaultConstructor(this Type type, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance)
{
var constructor = type.GetEffectivelyDefaultConstructor(bindingFlags);
if(constructor is null)
return false;
var parameters = constructor.GetParameters();
if(parameters.Length == 0)
return constructor.Invoke(null);
object?[] args = [.. parameters.Select(p => p.DefaultValue)];
return constructor.Invoke(args);
}
public static bool IsNullableValueType(this Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
public static bool IsUnmanaged(this Type type)
{
if(!type.IsValueType)
return false;
try
{
_unmanagedGenericType.MakeGenericType(type);
return true;
}
catch
{
return false;
}
}
public static bool CanBeGenericParameter(this Type chosenType, Type genericParameter, Type?[]? selectedArgs)
{
if(!genericParameter.IsGenericParameter)
throw new ArgumentException("Not generic parameter.", nameof(genericParameter));
if(chosenType.IsAbstract && chosenType.IsSealed) // static classes can't be generic parameters
return false;
if(chosenType == typeof(void))
return false;
var attributes = genericParameter.GenericParameterAttributes;
if(attributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint))
{
// where T : struct
if(!chosenType.IsValueType || chosenType.IsNullableValueType())
return false;
// where T : unmanaged
bool isUnmanagedConstraint = genericParameter.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.UnmanagedTypeAttribute");
if(isUnmanagedConstraint && !chosenType.IsUnmanaged())
return false;
}
// where T : class
if(attributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) &&
chosenType.IsValueType)
return false;
// where T : new()
if(attributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint))
{
if(chosenType.IsAbstract || chosenType.IsInterface)
return false;
if(!chosenType.IsValueType && chosenType.GetConstructor(BindingFlags.Public | BindingFlags.Instance, Type.EmptyTypes) == null)
return false;
}
// where T : allows ref struct
if(!attributes.HasFlag(GenericParameterAttributes.AllowByRefLike) && chosenType.IsByRefLike)
return false;
// where T : Type1, Type2, ...
Type? underlyingType = Nullable.GetUnderlyingType(chosenType);
var constraints = genericParameter.GetGenericParameterConstraints();
foreach(var constraint in constraints)
{
if(!TryMakeEffective(constraint, selectedArgs, out var effectiveConstraint))
return false;
var typeToTest = effectiveConstraint.IsInterface ? (underlyingType ?? chosenType) : chosenType;
if(!typeToTest.IsAssignableTo(effectiveConstraint))
return false;
}
return true;
}
private static bool TryMakeEffective(Type constraint, Type?[]? selectedArgs, out Type effectiveConstraint)
{
effectiveConstraint = null!;
// where T : U
if(constraint.IsGenericParameter)
{
int position = constraint.GenericParameterPosition;
if(selectedArgs is null ||
position >= selectedArgs.Length ||
selectedArgs[position] is null)
{
return false;
}
effectiveConstraint = selectedArgs[position]!;
return true;
}
// where T : IComparable<U>
if(constraint.IsGenericType && constraint.ContainsGenericParameters)
{
var genericArguments = constraint.GetGenericArguments();
for(int i = 0; i < genericArguments.Length; i++)
{
if(!TryMakeEffective(genericArguments[i], selectedArgs, out var effectiveArgument))
return false;
genericArguments[i] = effectiveArgument;
}
effectiveConstraint = constraint.GetGenericTypeDefinition().MakeGenericType(genericArguments);
return true;
}
effectiveConstraint = constraint;
return true;
}
}