Skip to content

Commit

Permalink
Optimize generic MethodInfo for Func<TResult>
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed Feb 13, 2020
1 parent ef12e13 commit 556baf9
Show file tree
Hide file tree
Showing 15 changed files with 273 additions and 70 deletions.
89 changes: 89 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncInstanceMethodInfo1`2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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.

#nullable enable

using System;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
/// <summary>
/// Represents the <see cref="MethodInfo"/> for a generic function corresponding to <see cref="Func{TResult}"/>,
/// with the following characteristics:
///
/// <list type="bullet">
/// <item><description>The method is an instance method on an object of type <typeparamref name="TTarget"/>.</description></item>
/// <item><description>One generic type argument.</description></item>
/// <item><description>A return value of <typeparamref name="TResult"/>.</description></item>
/// </list>
/// </summary>
/// <typeparam name="TTarget">The type of the receiver of the instance method.</typeparam>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
internal sealed class FuncInstanceMethodInfo1<TTarget, TResult> : FuncMethodInfo1<TResult>
where TTarget : class
{
private static readonly string _targetTypeCheckMessage = $"Should have a target type of '{typeof(TTarget)}'";

public FuncInstanceMethodInfo1(Func<TResult> function)
: this(function.Method)
{
}

private FuncInstanceMethodInfo1(MethodInfo methodInfo)
: base(methodInfo)
{
Contracts.CheckParam(!GenericMethodDefinition.IsStatic, nameof(methodInfo), "Should be an instance method");
Contracts.CheckParam(GenericMethodDefinition.DeclaringType == typeof(TTarget), nameof(methodInfo), _targetTypeCheckMessage);
}

/// <summary>
/// Creates a <see cref="FuncInstanceMethodInfo1{TTarget, TResult}"/> representing the <see cref="MethodInfo"/> for
/// a generic instance method. This helper method allows the instance to be created prior to the creation of any
/// instances of the target type. The following example shows the creation of an instance representing the
/// <see cref="object.GetHashCode"/> method:
///
/// <code>
/// FuncInstanceMethodInfo1&lt;object, int&gt;.Create(obj => obj.GetHashCode)
/// </code>
/// </summary>
/// <param name="expression">The expression which creates the delegate for an instance of the target type.</param>
/// <returns>A <see cref="FuncInstanceMethodInfo1{TTarget, TResult}"/> representing the <see cref="MethodInfo"/>
/// for the generic instance method.</returns>
public static FuncInstanceMethodInfo1<TTarget, TResult> Create(Expression<Func<TTarget, Func<TResult>>> expression)
{
if (!(expression is { Body: UnaryExpression { Operand: MethodCallExpression methodCallExpression } }))
{
throw Contracts.ExceptParam(nameof(expression), "Unexpected expression form");
}

// Verify that we are calling MethodInfo.CreateDelegate(Type, object)
Contracts.CheckParam(methodCallExpression.Method.DeclaringType == typeof(MethodInfo), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.Name == nameof(MethodInfo.CreateDelegate), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.GetParameters().Length == 2, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.GetParameters()[0].ParameterType == typeof(Type), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Method.GetParameters()[1].ParameterType == typeof(object), nameof(expression), "Unexpected expression form");

// Verify that we are creating a delegate of type Func<TRet>
Contracts.CheckParam(methodCallExpression.Arguments.Count == 2, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Arguments[0] is ConstantExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(((ConstantExpression)methodCallExpression.Arguments[0]).Type == typeof(Type), nameof(expression), "Unexpected expression form");
Contracts.CheckParam((Type)((ConstantExpression)methodCallExpression.Arguments[0]).Value == typeof(Func<TResult>), nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Arguments[1] is ParameterExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(methodCallExpression.Arguments[1] == expression.Parameters[0], nameof(expression), "Unexpected expression form");

// Check the MethodInfo
Contracts.CheckParam(methodCallExpression.Object is ConstantExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(((ConstantExpression)methodCallExpression.Object).Type == typeof(MethodInfo), nameof(expression), "Unexpected expression form");

var methodInfo = (MethodInfo)((ConstantExpression)methodCallExpression.Object).Value;
Contracts.CheckParam(expression.Body is UnaryExpression, nameof(expression), "Unexpected expression form");
Contracts.CheckParam(((UnaryExpression)expression.Body).Operand is MethodCallExpression, nameof(expression), "Unexpected expression form");

return new FuncInstanceMethodInfo1<TTarget, TResult>(methodInfo);
}
}
}
46 changes: 46 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncMethodInfo1`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.

#nullable enable

using System;
using System.Collections.Immutable;
using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
/// <summary>
/// Represents the <see cref="MethodInfo"/> for a generic function corresponding to <see cref="Func{TResult}"/>,
/// with the following characteristics:
///
/// <list type="bullet">
/// <item><description>One generic type argument.</description></item>
/// <item><description>A return value of <typeparamref name="TResult"/>.</description></item>
/// </list>
/// </summary>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
internal abstract class FuncMethodInfo1<TResult> : FuncMethodInfo<TResult>
{
private ImmutableDictionary<Type, MethodInfo> _instanceMethodInfo;

private protected FuncMethodInfo1(MethodInfo methodInfo)
: base(methodInfo)
{
_instanceMethodInfo = ImmutableDictionary<Type, MethodInfo>.Empty;

Contracts.CheckParam(GenericMethodDefinition.GetGenericArguments().Length == 1, nameof(methodInfo),
"Should have exactly one generic type parameter but does not");
}

public MethodInfo MakeGenericMethod(Type typeArg1)
{
return ImmutableInterlocked.GetOrAdd(
ref _instanceMethodInfo,
typeArg1,
(typeArg, methodInfo) => methodInfo.MakeGenericMethod(typeArg),
GenericMethodDefinition);
}
}
}
25 changes: 25 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncMethodInfo`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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.

#nullable enable

using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
internal abstract class FuncMethodInfo<TResult>
{
private protected FuncMethodInfo(MethodInfo methodInfo)
{
Contracts.CheckValue(methodInfo, nameof(methodInfo));
Contracts.CheckParam(methodInfo.IsGenericMethod, nameof(methodInfo), "Should be generic but is not");

GenericMethodDefinition = methodInfo.GetGenericMethodDefinition();
Contracts.CheckParam(GenericMethodDefinition.ReturnType == typeof(TResult), nameof(methodInfo), "Cannot be generic on return type");
}

protected MethodInfo GenericMethodDefinition { get; }
}
}
32 changes: 32 additions & 0 deletions src/Microsoft.ML.Core/Utilities/FuncStaticMethodInfo1`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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.

#nullable enable

using System;
using System.Reflection;
using Microsoft.ML.Runtime;

namespace Microsoft.ML.Internal.Utilities
{
/// <summary>
/// Represents the <see cref="MethodInfo"/> for a generic function corresponding to <see cref="Func{TResult}"/>,
/// with the following characteristics:
///
/// <list type="bullet">
/// <item><description>The method is static.</description></item>
/// <item><description>One generic type argument.</description></item>
/// <item><description>A return value of <typeparamref name="TResult"/>.</description></item>
/// </list>
/// </summary>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
internal sealed class FuncStaticMethodInfo1<TResult> : FuncMethodInfo1<TResult>
{
public FuncStaticMethodInfo1(Func<TResult> function)
: base(function.Method)
{
Contracts.CheckParam(GenericMethodDefinition.IsStatic, nameof(function), "Should be a static method");
}
}
}
46 changes: 29 additions & 17 deletions src/Microsoft.ML.Core/Utilities/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -988,20 +988,32 @@ private static MethodInfo MarshalInvokeCheckAndCreate<TRet>(Type[] genArgs, Dele
/// Because it is strongly typed, this can only be applied to methods whose return type
/// is known at compile time, that is, that do not depend on the type parameter of the method itself.
/// </summary>
/// <typeparam name="TRet">The return value</typeparam>
/// <typeparam name="TTarget">The type of the receiver of the instance method.</typeparam>
/// <typeparam name="TResult">The type of the return value of the method.</typeparam>
/// <param name="func">A delegate that should be a generic method with a single type parameter.
/// The generic method definition will be extracted, then a new method will be created with the
/// given type parameter, then the method will be invoked.</param>
/// <param name="target">The target of the invocation.</param>
/// <param name="genArg">The new type parameter for the generic method</param>
/// <returns>The return value of the invoked function</returns>
public static TRet MarshalInvoke<TRet>(Func<TRet> func, Type genArg)
public static TResult MarshalInvoke<TTarget, TResult>(FuncInstanceMethodInfo1<TTarget, TResult> func, TTarget target, Type genArg)
where TTarget : class
{
var meth = MarshalInvokeCheckAndCreate<TRet>(genArg, func);
return (TRet)meth.Invoke(func.Target, null);
var meth = func.MakeGenericMethod(genArg);
return (TResult)meth.Invoke(target, null);
}

/// <summary>
/// A static version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TResult MarshalInvoke<TResult>(FuncStaticMethodInfo1<TResult> func, Type genArg)
{
var meth = func.MakeGenericMethod(genArg);
return (TResult)meth.Invoke(null, null);
}

/// <summary>
/// A one-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A one-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TRet>(Func<TArg1, TRet> func, Type genArg, TArg1 arg1)
{
Expand All @@ -1010,7 +1022,7 @@ public static TRet MarshalInvoke<TArg1, TRet>(Func<TArg1, TRet> func, Type genAr
}

/// <summary>
/// A two-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A two-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TRet>(Func<TArg1, TArg2, TRet> func, Type genArg, TArg1 arg1, TArg2 arg2)
{
Expand All @@ -1019,7 +1031,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TRet>(Func<TArg1, TArg2, TRet> fu
}

/// <summary>
/// A three-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A three-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TRet>(Func<TArg1, TArg2, TArg3, TRet> func, Type genArg,
TArg1 arg1, TArg2 arg2, TArg3 arg3)
Expand All @@ -1029,7 +1041,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TRet>(Func<TArg1, TArg2, T
}

/// <summary>
/// A four-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A four-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
Expand All @@ -1039,7 +1051,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TRet>(Func<TArg1, T
}

/// <summary>
/// A five-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A five-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5)
Expand All @@ -1049,7 +1061,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TRet>(Func<T
}

/// <summary>
/// A six-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A six-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6)
Expand All @@ -1059,7 +1071,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TRet>
}

/// <summary>
/// A seven-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A seven-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7)
Expand All @@ -1069,7 +1081,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// An eight-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// An eight-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TRet>(Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TRet> func,
Type genArg, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8)
Expand All @@ -1079,7 +1091,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// A nine-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A nine-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TRet>(
Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TRet> func,
Expand All @@ -1090,7 +1102,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// A ten-argument version of <see cref="MarshalInvoke{TRet}"/>.
/// A ten-argument version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TRet>(
Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TRet> func,
Expand All @@ -1101,7 +1113,7 @@ public static TRet MarshalInvoke<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7
}

/// <summary>
/// A 1 argument and n type version of <see cref="MarshalInvoke{TRet}"/>.
/// A 1 argument and n type version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TRet>(
Func<TArg1, TRet> func,
Expand All @@ -1112,7 +1124,7 @@ public static TRet MarshalInvoke<TArg1, TRet>(
}

/// <summary>
/// A 2 argument and n type version of <see cref="MarshalInvoke{TRet}"/>.
/// A 2 argument and n type version of <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>.
/// </summary>
public static TRet MarshalInvoke<TArg1, TArg2, TRet>(
Func<TArg1, TArg2, TRet> func,
Expand Down Expand Up @@ -1147,7 +1159,7 @@ private static MethodInfo MarshalActionInvokeCheckAndCreate(Type[] typeArguments
}

/// <summary>
/// This is akin to <see cref="MarshalInvoke{TRet}(Func{TRet}, Type)"/>, except applied to
/// This is akin to <see cref="MarshalInvoke{TTarget, TResult}(FuncInstanceMethodInfo1{TTarget, TResult}, TTarget, Type)"/>, except applied to
/// <see cref="Action"/> instead of <see cref="Func{TRet}"/>.
/// </summary>
/// <param name="act">A delegate that should be a generic method with a single type parameter.
Expand Down
5 changes: 4 additions & 1 deletion src/Microsoft.ML.Data/DataLoadSave/Binary/BinaryLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,9 @@ public DataViewRowCursor[] GetRowCursorSet(IEnumerable<DataViewSchema.Column> co

private sealed class Cursor : RootCursorBase
{
private static readonly FuncInstanceMethodInfo1<Cursor, Delegate> _noRowGetterMethodInfo
= FuncInstanceMethodInfo1<Cursor, Delegate>.Create(target => target.NoRowGetter<int>);

private readonly BinaryLoader _parent;
private readonly int[] _colToActivesIndex;
private readonly TableOfContentsEntry[] _actives;
Expand Down Expand Up @@ -2071,7 +2074,7 @@ public override ValueGetter<TValue> GetGetter<TValue>(DataViewSchema.Column colu
/// a delegate that simply always throws.
/// </summary>
private Delegate GetNoRowGetter(DataViewType type)
=> Utils.MarshalInvoke(NoRowGetter<int>, type.RawType);
=> Utils.MarshalInvoke(_noRowGetterMethodInfo, this, type.RawType);

private Delegate NoRowGetter<T>()
{
Expand Down
Loading

0 comments on commit 556baf9

Please sign in to comment.