-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
LoadableClassAttribute.cs
236 lines (203 loc) · 12.1 KB
/
LoadableClassAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// 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;
}
}
}