找到适合的构造函数使用反射?

huangapple go评论91阅读模式
英文:

Find suitable Constructor with reflection?

问题

我在寻找传递的参数依赖于正确的构造函数时遇到了一些反射问题。

到目前为止,这段代码在传递的构造函数参数不为 null 时运行良好:

public static ConstructorInfo? FindSuitableConstructor(Type typeWithConstructorToFind, object?[] constructorArguments)
{
    return typeWithConstructorToFind.GetConstructor(
        constructorArguments.Select(constructorArgumentInstance => constructorArgumentInstance.GetType())
        .ToArray());
}

但是一旦涉及到 null,它就会崩溃,因为无法在 null 上调用 GetType()

有哪些替代方法来找到合适的构造函数?我也考虑过检查参数的数量以及参数类型(但同样,在 null 上无法工作),但到目前为止还没有更接近我要找的内容。

我知道 Activator.CreateInstance(typeWithConstructorToFind, constructorArguments) 可以以某种方式找到正确的构造函数,但我不需要该类型的实例,我真的需要的是 ConstructorInfo

英文:

I am having some reflection issues, when finding the right constructor depending on the passed parameters.

So far this piece of code works fine, when the constructor arguments that are passed are not null:

public static ConstructorInfo? FindSuitableConstructor(Type typeWithConstructorToFind, object?[] constructorArguments)
{
	return typeWithConstructorToFind.GetConstructor(
			constructorArguments.Select(constructorArgumentInstance => constructorArgumentInstance.GetType())
			.ToArray());
}

But as soon as there is null involved it crashes as it can not call GetType() on null.

What are the alternatives to find the suitable constructor? I was thinking also of checking the numbers of arguments and also the parameter types (but again, it does not work on null) but so far not closer to what I am looking for.

I know that Activator.CreateInstance(typeWithConstructorToFind, constructorArguments) can find the right constructor somehow, but I don't need the instace of that type, I really need the ConstructorInfo.

答案1

得分: 4

你可以尝试使用Binder.BindToMethod

public static ConstructorInfo? FindSuitableConstructor(Type typeWithConstructorToFind, object[] constructorArguments) {
    if (constructorArguments == null)
        throw new ArgumentNullException("constructorArguments");
    var constructors = typeWithConstructorToFind.GetConstructors();
    var argsCopy = constructorArguments.ToArray();
    try {
        return (ConstructorInfo)Type.DefaultBinder.BindToMethod(BindingFlags.Instance | BindingFlags.Public, constructors, ref argsCopy, null, null, null, out _);
    }
    catch (MissingMemberException) {
        return null;
    }
}

它会根据传递的参数选择一组方法(在这种情况下是构造函数)中的最佳匹配项。这比自己尝试来做要好,因为存在微妙的情况。请注意,如果有多个构造函数与您的参数匹配,它不一定会失败。例如:

public class Test {
    public Test(long x, string y) {

    }

    public Test(long x, object y) {

    }
}

如果我们尝试:

FindSuitableConstructor(typeof(Test), new object[] { 1, null });

理论上它将匹配两者,但它将返回具有string参数的第一个构造函数,因为实际上您可以这样做:

new Test(1, null);

而编译器将选择string重载。然而,如果您有例如以下情况:

public class Test {
    public Test(long x, string y) {

    }

    public Test(long x, MethodInfo y) {

    }
}

然后在这种情况下相同的操作将失败,引发AmbiguousMatchException,因为实际上不可能进行选择(并且new Test(1, null)在这种情况下将无法编译)。

英文:

You can try to use Binder.BindToMethod:

public static ConstructorInfo? FindSuitableConstructor(Type typeWithConstructorToFind, object[] constructorArguments) {
    if (constructorArguments == null)
        throw new ArgumentNullException("constructorArguments");
    var constructors = typeWithConstructorToFind.GetConstructors();
    var argsCopy = constructorArguments.ToArray();
    try {
        return (ConstructorInfo)Type.DefaultBinder.BindToMethod(BindingFlags.Instance | BindingFlags.Public, constructors, ref argsCopy, null, null, null, out _);
    }
    catch (MissingMemberException) {
        return null;
    }
}

It will select the best match among a set of methods (in this case constructors) according to passed arguments. It's better than trying to do that yourself, because there are subtle cases. Note that it will not necessary fail if there are several constructors which match your arguments. For example:

public class Test {
    public Test(long x, string y) {

    }

    public Test(long x, object y) {

    }
}

If we try:

FindSuitableConstructor(typeof(Test), new object[] { 1, null });

which in theory matches both, it will return first constructor with string argument, because indeed you can do:

new Test(1, null);

And compiler will choose the string overload. However if you have for example this:

public class Test {
    public Test(long x, string y) {

    }

    public Test(long x, MethodInfo y) {

    }
}

Then the same will fail with AmbiguousMatchException, because indeed it's not possible to choose (and new Test(1, null) will not compile in this case).

答案2

得分: 1

不确定是否有内置方法可以实现这一点。但是您可以尝试手动查找构造函数。以下是一些开始的代码:

var objects = new object?[] { 1, null };
if (objects.All(o => o is not null))
{
    return ..; // 使用当前解决方案
}

var types = objects.Select(o => o?.GetType()).ToArray();
ConstructorInfo? candidate = null;
foreach (var constructorInfo in typeWithConstructorToFind.GetConstructors())
{
    var @params = constructorInfo.GetParameters();
    if (@params.Length != types.Length)
    {
        continue;
    }

    for (var index = 0; index < @params.Length; index++)
    {
        var parameterInfo = @params[index];
        var type = types[index];
        if ((type == null && !parameterInfo.ParameterType.IsValueType) || parameterInfo.ParameterType.IsAssignableFrom(type)) // todo - check for explicit casts
        {
            continue; // 可以传递 null 或类型匹配 - 继续参数检查
        }
        break;
    }
    
    // 所有参数满足条件
    if (candidate is null)
    {
        candidate = constructorInfo;
    }
    else
    {
        throw new AmbiguousMatchException();
    }
}    

return candidate;

如果您需要进一步的帮助或解释,请随时告诉我。

英文:

Not sure there is a build in method to allow achieve this. But you can an always try finding constructor manually. Something to start with:

var objects = new object?[] { 1, null };
if (objects.All(o =&gt; o is not null))
{
    return ..; // use current solution
}

var types = objects.Select(o =&gt; o?.GetType()).ToArray();
ConstructorInfo? candidate = null;
foreach (var constructorInfo in typeWithConstructorToFind.GetConstructors())
{
    var @params = constructorInfo.GetParameters();
    if (@params.Length != types.Length)
    {
        continue;
    }

    for (var index = 0; index &lt; @params.Length; index++)
    {
        var parameterInfo = @params[index];
        var type = types[index];
        if ((type == null &amp;&amp; !parameterInfo.ParameterType.IsValueType) || parameterInfo.ParameterType.IsAssignableFrom(type)) // todo - check for explicit casts
        {
            continue; // can pass null or type matches - continue params check
        }
        break;
    }
    
    // all params satisfy 
    if (candidate is null)
    {
        candidate = constructorInfo;
    }
    else
    {
        throw new AmbiguousMatchException();
    }
}    

return candidate;

huangapple
  • 本文由 发表于 2023年1月9日 19:11:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/75056433.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定