Repository navigation
Allow use of property-based row classes in ML.NET - #616
Conversation
|
|
||
| il.Emit(OpCodes.Ldarg_3); // push arg3 | ||
| il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
| il.Emit(OpCodes.Call, propertyInfo.GetGetMethod()); // push [stack top].[propertyInfo] |
There was a problem hiding this comment.
Call [](start = 28, length = 4)
Should it be just call or callvirt?
There was a problem hiding this comment.
For F# records "call" was fine, but you're right that for C# get/set we may need callvirt
There was a problem hiding this comment.
We could just check the property, right? If the property is virtual or abstract, then callvirt. If it isn't virtual, then just call?
In reply to: 206686593 [](ancestors = 206686593)
|
|
||
| il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
| il.Emit(OpCodes.Ldarg_2); // push arg2 | ||
| il.Emit(OpCodes.Call, propertyInfo.GetSetMethod()); // [stack top-1].[propertyInfo] <- [stack top] |
There was a problem hiding this comment.
Call [](start = 28, length = 4)
Same question regarding Call or Callvirt
| "Can't bind the IDataView column '{0}' of type '{1}' to field '{2}' of type '{3}'.", | ||
| col.ColumnName, realColType, col.FieldInfo.Name, col.FieldInfo.FieldType.FullName); | ||
| "Can't bind the IDataView column '{0}' of type '{1}' to field or property '{2}' of type '{3}'.", | ||
| col.ColumnName, realColType, col.MemberInfo.Name, col.OutputType.FullName); |
There was a problem hiding this comment.
OutputType [](start = 78, length = 10)
This look something suspicious. In case of IsComputed = true you will return something else rather than FieldType or PropertyType. #Resolved
|
Thank you for your work @dsyme! Basically to make sure you can read data into IDataView properly and if you decide to map IDataView on some class it get properly mapped. I know this is mostly to unblock F#, but I think it would be nice to have tests where you cover private getter/setter, class which inherit property from base class, class which override, readonly property, private property, and I guess lot of other cases, just to knew what allowed and what not. But if you need to be unblocked by this PR to continue working on F# support, I think tests coverage easily can be separate issue. |
| userType | ||
| .GetProperties(BindingFlags.Public | BindingFlags.Instance) | ||
| .Where(x => x.CanRead && x.CanWrite && x.GetIndexParameters().Length == 0) | ||
| .ToArray(); |
There was a problem hiding this comment.
Shouldn't need to ToArray here, when you are just going to concat it with fieldInfos and then ToArray that as well.
There was a problem hiding this comment.
I'll remove it, thanks
| var mappingAttr = memberInfo.GetCustomAttribute<ColumnAttribute>(); | ||
| if (mappingAttr == null) | ||
| throw Contracts.Except($"{field.Name} is missing ColumnAttribute"); | ||
| throw Contracts.Except($"field or property {memberInfo.Name} is missing ColumnAttribute"); |
There was a problem hiding this comment.
f [](start = 45, length = 1)
It's a totally nitpicking, but we prefer to start our sentences with Capital letter. #Resolved
| break; | ||
|
|
||
| default: | ||
| throw Contracts.ExceptNotSupp("expected a FieldInfo or a PropInfo"); |
There was a problem hiding this comment.
PropInfo [](start = 77, length = 8)
is it worth to be shorten?
Also can you start sentence with capital letter? #Resolved
There was a problem hiding this comment.
|
|
||
| if (fieldInfo == null) | ||
| if (memberInfo == null) | ||
| throw Contracts.ExceptParam(nameof(userSchemaDefinition), "No field with name '{0}' found in type '{1}'", |
There was a problem hiding this comment.
No field => No field or property
| return (Delegate)methInfoProp.Invoke(null, new object[] { propertyInfo, assignmentOpCodeProp }); | ||
|
|
||
| default: | ||
| throw Contracts.ExceptNotSupp("expected a FieldInfo or a PropInfo"); |
|
OK, all updated base on code review comments so far, thanks! @Ivanidzo4ka I've added some initial testing here, is this the sort of thing you were after? https://github.com/dotnet/machinelearning/pull/616/files#diff-de10d1dc0f5785ee3a7f0053c465fbdcR632 |
| var mb = new DynamicMethod("Peek", null, args, typeof(TOwn), true); | ||
| var il = mb.GetILGenerator(); | ||
| var minfo = propertyInfo.GetGetMethod(); | ||
| var opcode = minfo.IsVirtual ? OpCodes.Callvirt : OpCodes.Call; |
There was a problem hiding this comment.
Do we need to handle IsAbstract?
There was a problem hiding this comment.
I'm not 100% sure, I thought IsAbstract implies IsVirtual, but the MSDN docs don't say that so I'll add it
| for (int index = 0; index < fields.Length; index++) | ||
| var userType = typeof(TInput); | ||
|
|
||
| var fieldInfos = userType.GetFields(); |
There was a problem hiding this comment.
Should we be checking for public and instance fields here? Just like we are in GetProperties.
eerhardt
left a comment
There was a problem hiding this comment.
I think this looks good, thanks for the help, @dsyme.
@TomFinley @Zruty0 - any concerns on merging this?
Public is the default - but yes, I think we should be checking for instance fields (I assume static fields cause failures at the moment) #Closed |
|
|
||
| default: | ||
| throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); | ||
| } |
There was a problem hiding this comment.
As far as I understand, this clause is only present because the compiler complains that there must be a default clause, so we actually will never trigger it.
If that's the case, I suggest putting Contracts.Assert(false) before throwing, to signify that this is a perceived-never-to-execute code (as opposed to something we don't support not but may in the future). #Closed
| break; | ||
|
|
||
| default: | ||
| throw Contracts.ExceptNotSupp("Expected a FieldInfo or a PropertyInfo"); |
There was a problem hiding this comment.
throw [](start = 20, length = 5)
Same here: if the code is never expected to execute, make it obvious to reader by putting Contracts.Assert(false) ahead of throw. #Closed
|
|
||
| il.Emit(OpCodes.Ldarg_3); // push arg3 | ||
| il.Emit(OpCodes.Ldarg_1); // push arg1 | ||
| il.Emit(opcode, minfo); // push [stack top].[propertyInfo] |
There was a problem hiding this comment.
push [stack top].[propertyInfo] [](start = 55, length = 31)
Please reconcile the comment with the code #Closed
There was a problem hiding this comment.
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.
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)
| 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.
throw [](start = 20, length = 5)
Contracts.Assert(false) before throwing #Closed
|
I think we should be consistent with public vs instance. If we do so for properties, do so for fields as well. In reply to: 409618178 [](ancestors = 409618178) |
|
In general, we try to merge the completely tested code (unless we're blocking other work, in which case we can use discretion). In reply to: 409402126 [](ancestors = 409402126) |
|
In my view, we should require both getter and setter to be public. Either that, or we have to split In reply to: 409383628 [](ancestors = 409383628) |
|
@Zruty0 All updated - the tests have now been completed #Closed |
|
I will also add one more F# for another way to define F# properties #Closed |
Done |
|
@dsyme thanks a lot for the contribution! Looks good to me now. |
This is WIP to address
It builds on #600 and you can see the added diff between #600 and this PR here
Copying comment from here: