-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Allow use of property-based row classes in ML.NET #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
fb536fd
e9040e9
8ec2fd5
192c8ba
968766f
9bea7e8
382906e
4e131b5
31eefbc
b332952
47db808
d214c4e
dc76490
f920204
747e417
710fe14
4e19c25
6487d49
431ad89
dd4efe4
3d53559
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,14 +51,30 @@ private static OpCode GetAssignmentOpCode(Type t) | |
/// </summary> | ||
internal static Delegate GeneratePeek<TOwn, TRow>(InternalSchemaDefinition.Column column) | ||
{ | ||
var fieldInfo = column.FieldInfo; | ||
Type fieldType = fieldInfo.FieldType; | ||
|
||
var assignmentOpCode = GetAssignmentOpCode(fieldType); | ||
Func<FieldInfo, OpCode, Delegate> func = GeneratePeek<TOwn, TRow, int>; | ||
var methInfo = func.GetMethodInfo().GetGenericMethodDefinition() | ||
.MakeGenericMethod(typeof(TOwn), typeof(TRow), fieldType); | ||
return (Delegate)methInfo.Invoke(null, new object[] { fieldInfo, assignmentOpCode }); | ||
switch (column.MemberInfo) | ||
{ | ||
case FieldInfo fieldInfo: | ||
Type fieldType = fieldInfo.FieldType; | ||
|
||
var assignmentOpCode = GetAssignmentOpCode(fieldType); | ||
Func<FieldInfo, OpCode, Delegate> func = GeneratePeek<TOwn, TRow, int>; | ||
var methInfo = func.GetMethodInfo().GetGenericMethodDefinition() | ||
.MakeGenericMethod(typeof(TOwn), typeof(TRow), fieldType); | ||
return (Delegate)methInfo.Invoke(null, new object[] { fieldInfo, assignmentOpCode }); | ||
|
||
case PropertyInfo propertyInfo: | ||
Type propertyType = propertyInfo.PropertyType; | ||
|
||
var assignmentOpCodeProp = GetAssignmentOpCode(propertyType); | ||
Func<PropertyInfo, OpCode, Delegate> funcProp = GeneratePeek<TOwn, TRow, int>; | ||
var methInfoProp = funcProp.GetMethodInfo().GetGenericMethodDefinition() | ||
.MakeGenericMethod(typeof(TOwn), typeof(TRow), propertyType); | ||
return (Delegate)methInfoProp.Invoke(null, new object[] { propertyInfo, assignmentOpCodeProp }); | ||
|
||
default: | ||
throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); | ||
|
||
} | ||
} | ||
|
||
private static Delegate GeneratePeek<TOwn, TRow, TValue>(FieldInfo fieldInfo, OpCode assignmentOpCode) | ||
|
@@ -81,21 +97,58 @@ private static Delegate GeneratePeek<TOwn, TRow, TValue>(FieldInfo fieldInfo, Op | |
return mb.CreateDelegate(typeof(Peek<TRow, TValue>)); | ||
} | ||
|
||
private static Delegate GeneratePeek<TOwn, TRow, TValue>(PropertyInfo propertyInfo, OpCode assignmentOpCode) | ||
{ | ||
// REVIEW: It seems like we really should cache these, instead of generating them per cursor. | ||
Type[] args = { typeof(TOwn), typeof(TRow), typeof(long), typeof(TValue).MakeByRefType() }; | ||
var mb = new DynamicMethod("Peek", null, args, typeof(TOwn), true); | ||
var il = mb.GetILGenerator(); | ||
var minfo = propertyInfo.GetGetMethod(); | ||
var opcode = (minfo.IsVirtual || minfo.IsAbstract) ? OpCodes.Callvirt : OpCodes.Call; | ||
|
||
il.Emit(OpCodes.Ldarg_3); // push arg3 | ||
il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
il.Emit(opcode, minfo); // push [stack top].[propertyInfo] | ||
// Stobj needs to coupled with a type. | ||
if (assignmentOpCode == OpCodes.Stobj) // [stack top-1] = [stack top] | ||
il.Emit(assignmentOpCode, propertyInfo.PropertyType); | ||
else | ||
il.Emit(assignmentOpCode); | ||
il.Emit(OpCodes.Ret); // ret | ||
|
||
return mb.CreateDelegate(typeof(Peek<TRow, TValue>)); | ||
} | ||
|
||
/// <summary> | ||
/// Each of the specialized 'poke' methods sets the appropriate field value of an instance of T | ||
/// to the provided value. So, the call is 'peek(userObject, providedValue)' and the logic is | ||
/// indentical to 'userObject.##FIELD## = providedValue', where ##FIELD## is defined per poke method. | ||
/// </summary> | ||
internal static Delegate GeneratePoke<TOwn, TRow>(InternalSchemaDefinition.Column column) | ||
{ | ||
var fieldInfo = column.FieldInfo; | ||
Type fieldType = fieldInfo.FieldType; | ||
|
||
var assignmentOpCode = GetAssignmentOpCode(fieldType); | ||
Func<FieldInfo, OpCode, Delegate> func = GeneratePoke<TOwn, TRow, int>; | ||
var methInfo = func.GetMethodInfo().GetGenericMethodDefinition() | ||
.MakeGenericMethod(typeof(TOwn), typeof(TRow), fieldType); | ||
return (Delegate)methInfo.Invoke(null, new object[] { fieldInfo, assignmentOpCode }); | ||
switch (column.MemberInfo) | ||
{ | ||
case FieldInfo fieldInfo: | ||
Type fieldType = fieldInfo.FieldType; | ||
|
||
var assignmentOpCode = GetAssignmentOpCode(fieldType); | ||
Func<FieldInfo, OpCode, Delegate> func = GeneratePoke<TOwn, TRow, int>; | ||
var methInfo = func.GetMethodInfo().GetGenericMethodDefinition() | ||
.MakeGenericMethod(typeof(TOwn), typeof(TRow), fieldType); | ||
return (Delegate)methInfo.Invoke(null, new object[] { fieldInfo, assignmentOpCode }); | ||
|
||
case PropertyInfo propertyInfo: | ||
Type propertyType = propertyInfo.PropertyType; | ||
|
||
var assignmentOpCodeProp = GetAssignmentOpCode(propertyType); | ||
Func<PropertyInfo, Delegate> funcProp = GeneratePoke<TOwn, TRow, int>; | ||
var methInfoProp = funcProp.GetMethodInfo().GetGenericMethodDefinition() | ||
.MakeGenericMethod(typeof(TOwn), typeof(TRow), propertyType); | ||
return (Delegate)methInfoProp.Invoke(null, new object[] { propertyInfo }); | ||
|
||
default: | ||
throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contracts.Assert(false) before throwing #Closed |
||
} | ||
} | ||
|
||
private static Delegate GeneratePoke<TOwn, TRow, TValue>(FieldInfo fieldInfo, OpCode assignmentOpCode) | ||
|
@@ -115,5 +168,20 @@ private static Delegate GeneratePoke<TOwn, TRow, TValue>(FieldInfo fieldInfo, Op | |
il.Emit(OpCodes.Ret); // ret | ||
return mb.CreateDelegate(typeof(Poke<TRow, TValue>), null); | ||
} | ||
|
||
private static Delegate GeneratePoke<TOwn, TRow, TValue>(PropertyInfo propertyInfo) | ||
{ | ||
Type[] args = { typeof(TOwn), typeof(TRow), typeof(TValue) }; | ||
var mb = new DynamicMethod("Poke", null, args, typeof(TOwn), true); | ||
var il = mb.GetILGenerator(); | ||
var minfo = propertyInfo.GetSetMethod(); | ||
var opcode = (minfo.IsVirtual || minfo.IsAbstract) ? OpCodes.Callvirt : OpCodes.Call; | ||
|
||
il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
il.Emit(OpCodes.Ldarg_2); // push arg2 | ||
il.Emit(opcode, minfo); // [stack top-1].[propertyInfo] <- [stack top] | ||
il.Emit(OpCodes.Ret); // ret | ||
return mb.CreateDelegate(typeof(Poke<TRow, TValue>), null); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,21 +23,23 @@ internal sealed class InternalSchemaDefinition | |
public class Column | ||
{ | ||
public readonly string ColumnName; | ||
public readonly FieldInfo FieldInfo; | ||
public readonly MemberInfo MemberInfo; | ||
public readonly ParameterInfo ReturnParameterInfo; | ||
public readonly ColumnType ColumnType; | ||
public readonly bool IsComputed; | ||
public readonly Delegate Generator; | ||
private readonly Dictionary<string, MetadataInfo> _metadata; | ||
public Dictionary<string, MetadataInfo> Metadata { get { return _metadata; } } | ||
public Type ReturnType {get { return ReturnParameterInfo.ParameterType.GetElementType(); }} | ||
public Type ComputedReturnType {get { return ReturnParameterInfo.ParameterType.GetElementType(); }} | ||
public Type FieldOrPropertyType => (MemberInfo is FieldInfo) ? (MemberInfo as FieldInfo).FieldType : (MemberInfo as PropertyInfo).PropertyType; | ||
public Type OutputType => IsComputed ? ComputedReturnType : FieldOrPropertyType; | ||
|
||
public Column(string columnName, ColumnType columnType, FieldInfo fieldInfo) : | ||
this(columnName, columnType, fieldInfo, null, null) { } | ||
public Column(string columnName, ColumnType columnType, MemberInfo memberInfo) : | ||
this(columnName, columnType, memberInfo, null, null) { } | ||
|
||
public Column(string columnName, ColumnType columnType, FieldInfo fieldInfo, | ||
public Column(string columnName, ColumnType columnType, MemberInfo memberInfo, | ||
Dictionary<string, MetadataInfo> metadataInfos) : | ||
this(columnName, columnType, fieldInfo, null, metadataInfos) { } | ||
this(columnName, columnType, memberInfo, null, metadataInfos) { } | ||
|
||
public Column(string columnName, ColumnType columnType, Delegate generator) : | ||
this(columnName, columnType, null, generator, null) { } | ||
|
@@ -46,7 +48,7 @@ public Column(string columnName, ColumnType columnType, Delegate generator, | |
Dictionary<string, MetadataInfo> metadataInfos) : | ||
this(columnName, columnType, null, generator, metadataInfos) { } | ||
|
||
private Column(string columnName, ColumnType columnType, FieldInfo fieldInfo = null, | ||
private Column(string columnName, ColumnType columnType, MemberInfo memberInfo = null, | ||
Delegate generator = null, Dictionary<string, MetadataInfo> metadataInfos = null) | ||
{ | ||
Contracts.AssertNonEmpty(columnName); | ||
|
@@ -55,8 +57,8 @@ private Column(string columnName, ColumnType columnType, FieldInfo fieldInfo = n | |
|
||
if (generator == null) | ||
{ | ||
Contracts.AssertValue(fieldInfo); | ||
FieldInfo = fieldInfo; | ||
Contracts.AssertValue(memberInfo); | ||
MemberInfo = memberInfo; | ||
} | ||
else | ||
{ | ||
|
@@ -95,8 +97,8 @@ public void AssertRep() | |
// If Column is computed type, it must have a generator. | ||
Contracts.Assert(IsComputed == (Generator != null)); | ||
|
||
// Column must have either a generator or a fieldInfo value. | ||
Contracts.Assert((Generator == null) != (FieldInfo == null)); | ||
// Column must have either a generator or a memberInfo value. | ||
Contracts.Assert((Generator == null) != (MemberInfo == null)); | ||
|
||
// Additional Checks if there is a generator. | ||
if (Generator == null) | ||
|
@@ -115,9 +117,7 @@ public void AssertRep() | |
Contracts.Assert(Generator.GetMethodInfo().ReturnType == typeof(void)); | ||
|
||
// Checks that the return type of the generator is compatible with ColumnType. | ||
bool isVector; | ||
DataKind datakind; | ||
GetVectorAndKind(ReturnType, "return type", out isVector, out datakind); | ||
GetVectorAndKind(ComputedReturnType, "return type", out bool isVector, out DataKind datakind); | ||
Contracts.Assert(isVector == ColumnType.IsVector); | ||
Contracts.Assert(datakind == ColumnType.ItemType.RawKind); | ||
} | ||
|
@@ -131,19 +131,29 @@ private InternalSchemaDefinition(Column[] columns) | |
} | ||
|
||
/// <summary> | ||
/// Given a field info on a type, returns whether this appears to be a vector type, | ||
/// Given a field or property info on a type, returns whether this appears to be a vector type, | ||
/// and also the associated data kind for this type. If a data kind could not | ||
/// be determined, this will throw. | ||
/// </summary> | ||
/// <param name="fieldInfo">The field info to inspect.</param> | ||
/// <param name="memberInfo">The field or property info to inspect.</param> | ||
/// <param name="isVector">Whether this appears to be a vector type.</param> | ||
/// <param name="kind">The data kind of the type, or items of this type if vector.</param> | ||
public static void GetVectorAndKind(FieldInfo fieldInfo, out bool isVector, out DataKind kind) | ||
public static void GetVectorAndKind(MemberInfo memberInfo, out bool isVector, out DataKind kind) | ||
{ | ||
Contracts.AssertValue(fieldInfo); | ||
Type rawFieldType = fieldInfo.FieldType; | ||
var name = fieldInfo.Name; | ||
GetVectorAndKind(rawFieldType, name, out isVector, out kind); | ||
Contracts.AssertValue(memberInfo); | ||
switch (memberInfo) | ||
{ | ||
case FieldInfo fieldInfo: | ||
GetVectorAndKind(fieldInfo.FieldType, fieldInfo.Name, out isVector, out kind); | ||
break; | ||
|
||
case PropertyInfo propertyInfo: | ||
GetVectorAndKind(propertyInfo.PropertyType, propertyInfo.Name, out isVector, out kind); | ||
break; | ||
|
||
default: | ||
throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Same here: if the code is never expected to execute, make it obvious to reader by putting Contracts.Assert(false) ahead of throw. #Closed |
||
} | ||
} | ||
|
||
/// <summary> | ||
|
@@ -211,23 +221,27 @@ public static InternalSchemaDefinition Create(Type userType, SchemaDefinition us | |
|
||
bool isVector; | ||
DataKind kind; | ||
FieldInfo fieldInfo = null; | ||
MemberInfo memberInfo = null; | ||
|
||
if (!col.IsComputed) | ||
{ | ||
fieldInfo = userType.GetField(col.MemberName); | ||
memberInfo = userType.GetField(col.MemberName); | ||
|
||
if (memberInfo == null) | ||
memberInfo = userType.GetProperty(col.MemberName); | ||
|
||
if (fieldInfo == null) | ||
throw Contracts.ExceptParam(nameof(userSchemaDefinition), "No field with name '{0}' found in type '{1}'", | ||
if (memberInfo == null) | ||
throw Contracts.ExceptParam(nameof(userSchemaDefinition), "No field or property with name '{0}' found in type '{1}'", | ||
col.MemberName, | ||
userType.FullName); | ||
|
||
//Clause to handle the field that may be used to expose the cursor channel. | ||
//This field does not need a column. | ||
if (fieldInfo.FieldType == typeof(IChannel)) | ||
if ( (memberInfo is FieldInfo && (memberInfo as FieldInfo).FieldType == typeof(IChannel)) || | ||
(memberInfo is PropertyInfo && (memberInfo as PropertyInfo).PropertyType == typeof(IChannel))) | ||
continue; | ||
|
||
GetVectorAndKind(fieldInfo, out isVector, out kind); | ||
GetVectorAndKind(memberInfo, out isVector, out kind); | ||
} | ||
else | ||
{ | ||
|
@@ -268,7 +282,7 @@ public static InternalSchemaDefinition Create(Type userType, SchemaDefinition us | |
|
||
dstCols[i] = col.IsComputed ? | ||
new Column(colName, colType, col.Generator, col.Metadata) | ||
: new Column(colName, colType, fieldInfo, col.Metadata); | ||
: new Column(colName, colType, memberInfo, col.Metadata); | ||
|
||
} | ||
return new InternalSchemaDefinition(dstCols); | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please reconcile the comment with the code #Closed
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure what you mean here? The comment is roughly accurate, based on the similar code for fields #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant that the line 111 now emits a 'call' opcode, but the comment still says
push [stack top].[propertyInfo]
. Oh, but what you mean is that the result of call will be that the property value will end up on top of the stack.I suppose it's true, but I would expect the comment to look like
// call [methodInfo]
or something.In reply to: 206991828 [](ancestors = 206991828)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, done