|
@@ -240,6 +240,92 @@ namespace InABox.Core
|
|
|
|
|
|
public static bool HasAttribute<T>(this Type type) where T : Attribute => type.GetCustomAttribute<T>() != null;
|
|
|
|
|
|
+ public class GenericTypeBuilder
|
|
|
+ {
|
|
|
+ public Type Type { get; private set; }
|
|
|
+
|
|
|
+ public Type[] GenericTypeArguments { get; private set; }
|
|
|
+
|
|
|
+ private Type[] _genericArguments;
|
|
|
+
|
|
|
+ public GenericTypeBuilder(Type type)
|
|
|
+ {
|
|
|
+ Type = type;
|
|
|
+ _genericArguments = type.GetGenericArguments();
|
|
|
+ GenericTypeArguments = new Type[_genericArguments.Length];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Try to fill some type arguments based on the given <paramref name="arguments"/> to the <paramref name="interfaceType"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <b>Example:</b>
|
|
|
+ /// If <c>A<T1, T2></c> implements <c>I1<T1></c> and <c>I2<T2></c>, then calling
|
|
|
+ /// <c>AddInterface(typeof(I1<>), typeof(int))</c> will fill the <c>T1</c> slot - that is, we would produce
|
|
|
+ /// <c>A<int, T2></c>, and calling <c>AddInterface(typeof(I2<>), typeof(string))</c> will fill the <c>T2</c> slot with
|
|
|
+ /// <c>string</c>, such that if both are called, we will obtain <c>A<int, string></c>.
|
|
|
+ /// </remarks>
|
|
|
+ public GenericTypeBuilder AddInterface(Type interfaceType, params Type[] arguments)
|
|
|
+ {
|
|
|
+ Debug.Assert(arguments.Length == interfaceType.GetGenericArguments().Length, "Wrong number of arguments passed.");
|
|
|
+ if(Type.GetInterfaceDefinition(interfaceType) is Type inter)
|
|
|
+ {
|
|
|
+ for(int i = 0; i < arguments.Length; ++i)
|
|
|
+ {
|
|
|
+ var idx = Array.IndexOf(_genericArguments, inter.GenericTypeArguments[i]);
|
|
|
+ GenericTypeArguments[idx] = arguments[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Like <see cref="AddInterface(Type, Type[])"/>, but obtaining the arguments from <paramref name="otherType"/>'s implementation
|
|
|
+ /// of <paramref name="interfaceType"/>.
|
|
|
+ /// </summary>
|
|
|
+ public GenericTypeBuilder AddInterfaceFrom(Type interfaceType, Type otherType)
|
|
|
+ {
|
|
|
+ if(otherType.GetInterfaceDefinition(interfaceType) is Type inter)
|
|
|
+ {
|
|
|
+ AddInterface(interfaceType, inter.GenericTypeArguments);
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Build the generic type. If not all the type arguments are filled, this returns <see langword="null"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>
|
|
|
+ /// The completed type, or <see langword="null"/> if the method fails.
|
|
|
+ /// </returns>
|
|
|
+ public Type? Build()
|
|
|
+ {
|
|
|
+ if(GenericTypeArguments.Any(x => x is null))
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Type.MakeGenericType(GenericTypeArguments);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// See <see cref="Build"/>.
|
|
|
+ /// </summary>
|
|
|
+ public bool TryBuild([NotNullWhen(true)] out Type? result)
|
|
|
+ {
|
|
|
+ result = Build();
|
|
|
+ return result != null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Attempt to build this generic type into a full type, using the <see cref="GenericTypeBuilder"/> class,
|
|
|
+ /// allowing to fill generic type arguments based on interfaces.
|
|
|
+ /// </summary>
|
|
|
+ public static GenericTypeBuilder BuildGenericType(this Type type)
|
|
|
+ {
|
|
|
+ return new GenericTypeBuilder(type);
|
|
|
+ }
|
|
|
|
|
|
#endregion
|
|
|
|