File: ComponentModel\LoadableClassAttribute.cs
Web Access
Project: src\src\Microsoft.ML.Core\Microsoft.ML.Core.csproj (Microsoft.ML.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Linq;
using System.Reflection;
using Microsoft.ML.Internal.Utilities;
using Microsoft.ML.Runtime;
 
namespace Microsoft.ML;
 
/// <summary>
/// Common signature type with no extra parameters.
/// </summary>
[BestFriend]
internal delegate void SignatureDefault();
 
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
[BestFriend]
internal sealed class LoadableClassAttribute : LoadableClassAttributeBase
{
    /// <summary>
    /// Assembly attribute used to specify that a class is loadable by a machine learning
    /// host environment, such as TLC
    /// </summary>
    /// <param name="instType">The class type that is loadable</param>
    /// <param name="argType">The argument type that the constructor takes (may be null)</param>
    /// <param name="sigType">The signature of the constructor of this class (in addition to the arguments parameter)</param>
    /// <param name="userName">The name to use when presenting a list to users</param>
    /// <param name="loadNames">The names that can be used to load the class, for example, from a command line</param>
    public LoadableClassAttribute(Type instType, Type argType, Type sigType, string userName, params string[] loadNames)
        : base(null, instType, instType, argType, new[] { sigType }, userName, loadNames)
    {
    }
 
    /// <summary>
    /// Assembly attribute used to specify that a class is loadable by a machine learning
    /// host environment, such as TLC
    /// </summary>
    /// <param name="instType">The class type that is loadable</param>
    /// <param name="loaderType">The class type that contains the construction method</param>
    /// <param name="argType">The argument type that the constructor takes (may be null)</param>
    /// <param name="sigType">The signature of the constructor of this class (in addition to the arguments parameter)</param>
    /// <param name="userName">The name to use when presenting a list to users</param>
    /// <param name="loadNames">The names that can be used to load the class, for example, from a command line</param>
    public LoadableClassAttribute(Type instType, Type loaderType, Type argType, Type sigType, string userName, params string[] loadNames)
        : base(null, instType, loaderType, argType, new[] { sigType }, userName, loadNames)
    {
    }
 
    public LoadableClassAttribute(Type instType, Type argType, Type[] sigTypes, string userName, params string[] loadNames)
        : base(null, instType, instType, argType, sigTypes, userName, loadNames)
    {
    }
 
    public LoadableClassAttribute(Type instType, Type loaderType, Type argType, Type[] sigTypes, string userName, params string[] loadNames)
        : base(null, instType, loaderType, argType, sigTypes, userName, loadNames)
    {
    }
 
    /// <summary>
    /// Assembly attribute used to specify that a class is loadable by a machine learning
    /// host environment, such as TLC
    /// </summary>
    /// <param name="summary">The description summary of the class type</param>
    /// <param name="instType">The class type that is loadable</param>
    /// <param name="argType">The argument type that the constructor takes (may be null)</param>
    /// <param name="sigType">The signature of the constructor of this class (in addition to the arguments parameter)</param>
    /// <param name="userName">The name to use when presenting a list to users</param>
    /// <param name="loadNames">The names that can be used to load the class, for example, from a command line</param>
    public LoadableClassAttribute(string summary, Type instType, Type argType, Type sigType, string userName, params string[] loadNames)
        : base(summary, instType, instType, argType, new[] { sigType }, userName, loadNames)
    {
    }
 
    /// <summary>
    /// Assembly attribute used to specify that a class is loadable by a machine learning
    /// host environment, such as TLC
    /// </summary>
    /// <param name="summary">The description summary of the class type</param>
    /// <param name="instType">The class type that is loadable</param>
    /// <param name="loaderType">The class type that contains the construction method</param>
    /// <param name="argType">The argument type that the constructor takes (may be null)</param>
    /// <param name="sigType">The signature of the constructor of this class (in addition to the arguments parameter)</param>
    /// <param name="userName">The name to use when presenting a list to users</param>
    /// <param name="loadNames">The names that can be used to load the class, for example, from a command line</param>
    public LoadableClassAttribute(string summary, Type instType, Type loaderType, Type argType, Type sigType, string userName, params string[] loadNames)
        : base(summary, instType, loaderType, argType, new[] { sigType }, userName, loadNames)
    {
    }
 
    public LoadableClassAttribute(string summary, Type instType, Type argType, Type[] sigTypes, string userName, params string[] loadNames)
        : base(summary, instType, instType, argType, sigTypes, userName, loadNames)
    {
    }
 
    public LoadableClassAttribute(string summary, Type instType, Type loaderType, Type argType, Type[] sigTypes, string userName, params string[] loadNames)
        : base(summary, instType, loaderType, argType, sigTypes, userName, loadNames)
    {
    }
}
 
internal abstract class LoadableClassAttributeBase : Attribute
{
    // Note: these properties have private setters to make attribute parsing easier - the values
    // are all guaranteed to be in the ConstructorArguments of the CustomAttributeData
    // (no named arguments).
 
    /// <summary>
    /// The type that is created/loaded.
    /// </summary>
    public Type InstanceType { get; private set; }
 
    /// <summary>
    /// The type that contains the construction method, whether static Instance property,
    /// static Create method, or constructor. Of course, a constructor is only permissible if
    /// this type derives from InstanceType. This defaults to the same as InstanceType.
    /// </summary>
    public Type LoaderType { get; private set; }
 
    /// <summary>
    /// The command line arguments object type. This should be null if there isn't one.
    /// </summary>
    public Type ArgType { get; private set; }
 
    /// <summary>
    /// This indicates the extra parameter types. It must be a delegate type. The return type should be void.
    /// The parameter types of the SigType delegate should NOT include the ArgType.
    /// </summary>
    public Type[] SigTypes { get; private set; }
 
    /// <summary>
    /// Note that CtorTypes includes the ArgType (if there is one), and the parameter types of the SigType.
    /// </summary>
    public Type[] CtorTypes { get; private set; }
 
    /// <summary>
    /// The description summary of the class type.
    /// </summary>
    public string Summary { get; private set; }
 
    /// <summary>
    /// UserName may be null or empty indicating that it should be hidden in UI.
    /// </summary>
    public string UserName { get; private set; }
    public string[] LoadNames { get; private set; }
 
    // REVIEW: This is out of step with the remainder of the class. However, my opinion is that the
    // LoadableClassAttribute class's design is worth reconsideration: having so many Type and string arguments
    // be defined *without names* in a constructor has led to enormous confusion.
 
    // REVIEW: Presumably it would be beneficial to have multiple documents.
 
    /// <summary>
    /// This should indicate a path within the <code>doc/public</code> directory next to the TLC
    /// solution, where the documentation lies. This value will be used as part of a URL, so,
    /// the path separator should be phrased as '/' forward slashes rather than backslashes.</summary>
    public string DocName { get; set; }
 
    protected LoadableClassAttributeBase(string summary, Type instType, Type loaderType, Type argType, Type[] sigTypes, string userName, params string[] loadNames)
    {
        Contracts.CheckValueOrNull(summary);
        Contracts.CheckValue(instType, nameof(instType));
        Contracts.CheckValue(loaderType, nameof(loaderType));
        Contracts.CheckNonEmpty(sigTypes, nameof(sigTypes));
 
        if (Utils.Size(loadNames) == 0)
            loadNames = new string[] { userName };
 
        if (loadNames.Any(s => string.IsNullOrWhiteSpace(s)))
            throw Contracts.ExceptEmpty(nameof(loadNames), "LoadableClass loadName parameter can't be empty");
 
        var sigType = sigTypes[0];
        Contracts.CheckValue(sigType, nameof(sigTypes));
        Type[] types;
        Contracts.CheckParam(sigType.BaseType == typeof(System.MulticastDelegate), nameof(sigTypes), "LoadableClass signature type must be a delegate type");
 
        var meth = sigType.GetMethod("Invoke");
        Contracts.CheckParam(meth != null, nameof(sigTypes), "LoadableClass signature type must be a delegate type");
        Contracts.CheckParam(meth.ReturnType == typeof(void), nameof(sigTypes), "LoadableClass signature type must be a delegate type with void return");
 
        var parms = meth.GetParameters();
        int itypeBase = 0;
 
        if (argType != null)
        {
            types = new Type[1 + parms.Length];
            types[itypeBase++] = argType;
        }
        else if (parms.Length > 0)
            types = new Type[parms.Length];
        else
            types = Type.EmptyTypes;
 
        for (int itype = 0; itype < parms.Length; itype++)
        {
            var parm = parms[itype];
            if ((parm.Attributes & (ParameterAttributes.Out | ParameterAttributes.Retval)) != 0)
                throw Contracts.Except("Invalid signature parameter attributes");
            types[itypeBase + itype] = parm.ParameterType;
        }
 
        for (int i = 1; i < sigTypes.Length; i++)
        {
            sigType = sigTypes[i];
            Contracts.CheckValue(sigType, nameof(sigTypes));
 
            Contracts.Check(sigType.BaseType == typeof(System.MulticastDelegate), "LoadableClass signature type must be a delegate type");
 
            meth = sigType.GetMethod("Invoke");
            Contracts.CheckParam(meth != null, nameof(sigTypes), "LoadableClass signature type must be a delegate type");
            Contracts.CheckParam(meth.ReturnType == typeof(void), nameof(sigTypes), "LoadableClass signature type must be a delegate type with void return");
            parms = meth.GetParameters();
            Contracts.CheckParam(parms.Length + itypeBase == types.Length, nameof(sigTypes), "LoadableClass signatures must have the same number of parameters");
            for (int itype = 0; itype < parms.Length; itype++)
            {
                var parm = parms[itype];
                if ((parm.Attributes & (ParameterAttributes.Out | ParameterAttributes.Retval)) != 0)
                    throw Contracts.ExceptParam(nameof(sigTypes), "Invalid signature parameter attributes");
                Contracts.CheckParam(types[itypeBase + itype] == parm.ParameterType, nameof(sigTypes),
                    "LoadableClass signatures must have the same set of parameters");
            }
        }
 
        InstanceType = instType;
        LoaderType = loaderType;
        ArgType = argType;
        SigTypes = sigTypes;
        CtorTypes = types;
        Summary = summary;
        UserName = userName;
        LoadNames = loadNames;
    }
}