IT story

리플렉션을 통해 선택적 매개 변수로 메서드 호출

hot-time 2021. 1. 5. 19:14
반응형

리플렉션을 통해 선택적 매개 변수로 메서드 호출


선택적 매개 변수와 함께 C # 4.0을 사용하여 다른 문제가 발생했습니다.

ConstructorInfo매개 변수가 필요하지 않다는 것을 알고 있는 함수 (또는 생성자, 객체가 있음)를 어떻게 호출 합니까?

지금 사용하는 코드는 다음과 같습니다.

type.GetParameterlessConstructor()
    .Invoke(BindingFlags.OptionalParamBinding | 
            BindingFlags.InvokeMethod | 
            BindingFlags.CreateInstance, 
            null, 
            new object[0], 
            CultureInfo.InvariantCulture);

(방금 다른 시도했습니다 BindingFlags).

GetParameterlessConstructor내가 작성한 사용자 지정 확장 방법입니다 Type.


MSDN 에 따르면 기본 매개 변수를 사용하려면 전달해야합니다 Type.Missing.

생성자에 3 개의 선택적 인수가있는 경우 빈 개체 배열을 전달하는 대신 각 요소의 값이 Type.Missing3 개 요소 개체 배열을 전달합니다 . 예 :

type.GetParameterlessConstructor()
    .Invoke(BindingFlags.OptionalParamBinding | 
            BindingFlags.InvokeMethod | 
            BindingFlags.CreateInstance, 
            null, 
            new object[] { Type.Missing, Type.Missing, Type.Missing }, 
            CultureInfo.InvariantCulture);

선택적 매개 변수는 일반 속성으로 표시되며 컴파일러에 의해 처리됩니다.
IL에 영향을주지 않으며 (메타 데이터 플래그 제외) 리플렉션에서 직접 지원하지 않습니다 ( IsOptionalDefaultValue속성 제외 ).

리플렉션과 함께 선택적 매개 변수를 사용하려면 기본값을 수동으로 전달해야합니다.


코드를 추가하겠습니다. 왜냐하면. 코드는 만족스럽지 않지만 동의하지만 상당히 간단합니다. 바라건대 이것은 이것을 건너는 사람을 도울 것입니다. 프로덕션 환경에서 원하는 만큼은 아니지만 테스트되었습니다.

인수 args를 사용하여 객체 obj에서 methodName 메서드 호출 :

    public Tuple<bool, object> Evaluate(IScopeContext c, object obj, string methodName, object[] args)
    {
        // Get the type of the object
        var t = obj.GetType();
        var argListTypes = args.Select(a => a.GetType()).ToArray();

        var funcs = (from m in t.GetMethods()
                     where m.Name == methodName
                     where m.ArgumentListMatches(argListTypes)
                     select m).ToArray();

        if (funcs.Length != 1)
            return new Tuple<bool, object>(false, null);

        // And invoke the method and see what we can get back.
        // Optional arguments means we have to fill things in.
        var method = funcs[0];
        object[] allArgs = args;
        if (method.GetParameters().Length != args.Length)
        {
            var defaultArgs = method.GetParameters().Skip(args.Length)
                .Select(a => a.HasDefaultValue ? a.DefaultValue : null);
            allArgs = args.Concat(defaultArgs).ToArray();
        }
        var r = funcs[0].Invoke(obj, allArgs);
        return new Tuple<bool, object>(true, r);
    }

그리고 ArgumentListMatches 함수는 기본적으로 GetMethod에서 찾을 수있는 논리를 대신합니다.

    public static bool ArgumentListMatches(this MethodInfo m, Type[] args)
    {
        // If there are less arguments, then it just doesn't matter.
        var pInfo = m.GetParameters();
        if (pInfo.Length < args.Length)
            return false;

        // Now, check compatibility of the first set of arguments.
        var commonArgs = args.Zip(pInfo, (margs, pinfo) => Tuple.Create(margs, pinfo.ParameterType));
        if (commonArgs.Where(t => !t.Item1.IsAssignableFrom(t.Item2)).Any())
            return false;

        // And make sure the last set of arguments are actually default!
        return pInfo.Skip(args.Length).All(p => p.IsOptional);
    }

Lots of LINQ, and this has not been performance tested!

Also, this will not handle generic function or method calls. That makes this significantly more ugly (as in repeated GetMethod calls).


All questions disappear as you see your code decompiled:

c#:

public MyClass([Optional, DefaultParameterValue("")]string myOptArg)

msil:

.method public hidebysig specialname rtspecialname instance void .ctor([opt]string myOptArg) cil managed 

As you see, optional parameter is a real separate entity that is decorated with specific attributes and has to be respected accordingly when invoking via reflection, as described earlier.


With the opensource framework ImpromptuInterface as of version 4 you can use the DLR in C# 4.0 to invoke constructors in a very late bound way and it's totally aware of constructors with named/optional arguments, this runs 4 times faster than Activator.CreateInstance(Type type, params object[] args) and you don't have to reflect the default values.

using ImpromptuInterface;
using ImpromptuInterface.InvokeExt;

...

//if all optional and you don't want to call any
Impromptu.InvokeConstructor(type)

or

//If you want to call one parameter and need to name it
Impromptu.InvokeConstructor(type, CultureInfo.InvariantCulture.WithArgumentName("culture"))

ReferenceURL : https://stackoverflow.com/questions/2421994/invoking-methods-with-optional-parameters-through-reflection

반응형