【问题标题】:Using a PropertyGrid to input method parameters使用 PropertyGrid 输入方法参数
【发布时间】:2026-01-24 09:50:01
【问题描述】:

我想使用 PropertyGrid 来输入方法参数。

我有一些应用程序会动态加载用户的 DLL 并调用具有特定签名(已知返回类型)的方法。

我想向用户展示使用 PropertyGrid 控件轻松将参数输入到被调用方法的选项。

问题是——PropertyGrid 作用于对象,而不作用于方法。

我想以某种方式在运行时将方法“转换”为具有反映其参数的属性的对象,在调用它时将输入值传递给方法。

当然我想要类型验证等(如果由 PropertyGrid 提供,现在不记得了)。

有什么简单的解决办法吗?

谢谢!

【问题讨论】:

  • 我已经启动了 LinqPad 并使用 CodeDom 构建了一个示例,但我不想为有这种态度的人发布这个。 (我不是在追分,只是感觉不对..)
  • 你指的是我的态度吗?我是新来的,我不知道我需要将其标记为已接受。我将几个答案标记为“已接受”,但这仍然没有在这里更新。很抱歉给您添麻烦

标签: c#


【解决方案1】:

这就是我昨天写的。 它旨在在LinqPad 中运行,这是一个很棒的免费工具,用于测试 linq 查询或编码 sn-ps。 (通过廉价升级获得智能感知)

代码应该告诉您如何处理不同类型的参数(ref、out)以及您是否正在调用实例方法。 (翻转 Main 中的 cmets 以测试实例方法)

在 LinqPad 中,您可以使用 Dump() 扩展方法让它在结果窗口中显示您的对象。这很方便查看实际发生的情况。

所以,如果您想知道如何动态地构造一个类型并调用它,这应该可以帮助您入门:

编辑:我完全忘了提及,您确实需要将这 2 个命名空间添加到查询中。您可以通过点击 F4->additional namespace imports 并添加以下 2 个来做到这一点:
System.CodeDom.Compiler
System.CodeDom

public static String TestMethod1(int a, ref int X, out string t)
{
    a += X;
    X = a * 2;
    t = "...>" + (X + a);
    return a.ToString() + "...";
}

public class TestClass
{
    public int SomeMethod(int a, DateTime? xyz)
    {
      if(xyz != null)
            a+= xyz.GetValueOrDefault().Day;
        return 12 + a;
    }
}

void Main()
{
    var sb = new StringBuilder();

    var methodInfo = typeof(UserQuery).GetMethod("TestMethod1");

    dynamic instance = CreateWrapper(methodInfo, sb);

    instance.a = 11;
    instance.X = 2;

    instance.CallMethod();

    /*
    var methodInfo = typeof(TestClass).GetMethod("SomeMethod");

    dynamic instance = CreateWrapper(methodInfo, sb);

    instance.a = 11;
    instance.xyz = new DateTime(2010, 1, 2);

    instance.CallMethod(new TestClass());
*/
    ((Object)instance).Dump();

    sb.ToString().Dump();
}

static object CreateWrapper(MethodInfo methodInfo, StringBuilder sb)
{
  // pick either C#, VB or another language that can handle generics
    var codeDom = CodeDomProvider.CreateProvider("C#");

    var unit = new CodeCompileUnit();
    var codeNameSpace = new CodeNamespace();
    codeNameSpace.Name = "YourNamespace";
    var wrapperType = AddWrapperType(codeDom, codeNameSpace, methodInfo, "WrapperType",     "MethodResultValue");

    unit.Namespaces.Add(codeNameSpace);

    // this is only needed so that LinqPad can dump the code
    codeDom.GenerateCodeFromNamespace(codeNameSpace, new StringWriter(sb), new CodeGeneratorOptions());

    // put the temp assembly in LinqPad's temp folder
    var outputFileName = Path.Combine(Path.GetDirectoryName(new Uri(typeof(UserQuery).Assembly.CodeBase).AbsolutePath), 
                                                                      Guid.NewGuid() + ".dll");
    var results = codeDom.CompileAssemblyFromDom(new CompilerParameters(new[]{new Uri(methodInfo.DeclaringType.Assembly.CodeBase).AbsolutePath,
                                                                              new Uri(typeof(UserQuery).Assembly.CodeBase).AbsolutePath,
                                                                              new Uri(typeof(UserQuery).BaseType.Assembly.CodeBase).AbsolutePath}.Distinct().ToArray(),
                                                                                                                                                        outputFileName), 
                                                                                             unit);
    results.Errors.Dump();
    new Uri(results.CompiledAssembly.CodeBase).AbsolutePath.Dump();

    if(results.Errors.Count == 0)
    {
        var compiledType = results.CompiledAssembly.GetType(codeNameSpace.Name + "." + wrapperType.Name);

        return Activator.CreateInstance(compiledType);
    }
    return null;
}


static CodeTypeDeclaration AddWrapperType(CodeDomProvider codeDom, 
                                          CodeNamespace codeNameSpace, 
                                                                                    MethodInfo methodInfo, 
                                                                                    string typeName, 
                                                                                    string resultPropertyName)
{
    var parameters = (from parameter in methodInfo.GetParameters()
                      select parameter).ToList();

    var returnValue = methodInfo.ReturnType;

    if(!String.IsNullOrEmpty(methodInfo.DeclaringType.Namespace))
        codeNameSpace.Imports.Add(new CodeNamespaceImport(methodInfo.DeclaringType.Namespace));

    var wrapperType = new CodeTypeDeclaration(typeName);

    var defaultAttributes = MemberAttributes.Public | MemberAttributes.Final;
    var thisRef = new CodeThisReferenceExpression();

    Func<Type, Type> getRealType = t => t.IsByRef || t.IsPointer ? t.GetElementType(): t;


    Func<String, String> getFieldName = parameterName => "m_" + parameterName + "_Field";


    Action<ParameterInfo> addProperty = p =>
    {
        var realType = getRealType(p.ParameterType);
        var usedName = p.Position == -1 ? resultPropertyName : p.Name;

        wrapperType.Members.Add(new CodeMemberField
        {
            Name = getFieldName(usedName),
            Type = new CodeTypeReference(realType),
            Attributes= MemberAttributes.Private
        });

        var property = new CodeMemberProperty
                                        {
                                            Name = usedName,
                                            Type = new CodeTypeReference(realType),
                                          Attributes= defaultAttributes
                                        };

        property.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(thisRef,  
                                                                                                  getFieldName(usedName))));
        property.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(thisRef,  getFieldName(usedName)), 
                                                           new CodeArgumentReferenceExpression("value")));

        wrapperType.Members.Add(property);
    };

    parameters.ForEach(addProperty);
    if(methodInfo.ReturnParameter != null)
    {
        addProperty(methodInfo.ReturnParameter);
    }

    var callMethod = new CodeMemberMethod
    {
        Name="CallMethod", 
        Attributes=defaultAttributes
    };

    CodeMethodInvokeExpression invokeExpr;

    if(!methodInfo.IsStatic)
    {
        callMethod.Parameters.Add(new CodeParameterDeclarationExpression(methodInfo.DeclaringType, 
                                                                         "instance"));
        invokeExpr = new CodeMethodInvokeExpression(new CodeArgumentReferenceExpression("instance"),
                                                    methodInfo.Name);
    }
    else
        invokeExpr = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(methodInfo.DeclaringType), methodInfo.Name);

    foreach(var parameter in parameters)
    {
        CodeExpression fieldExpression = new CodeFieldReferenceExpression(thisRef, 
                                                                          getFieldName(parameter.Name));

        if(parameter.ParameterType.IsByRef && !parameter.IsOut)
            fieldExpression = new CodeDirectionExpression(FieldDirection.Ref, fieldExpression);
        else if(parameter.IsOut)
            fieldExpression = new CodeDirectionExpression(FieldDirection.Out, fieldExpression);
        else if(parameter.IsIn)
           fieldExpression = new CodeDirectionExpression(FieldDirection.In, fieldExpression);

        invokeExpr.Parameters.Add(fieldExpression);
    }

    wrapperType.Members.Add(callMethod);

    if(returnValue != typeof(void))
        callMethod.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(thisRef,
                                                                                           getFieldName(resultPropertyName)),
                                                                                                        invokeExpr));
    else
        callMethod.Statements.Add(invokeExpr);

    codeNameSpace.Types.Add(wrapperType);

    return wrapperType;
}

【讨论】:

  • 哇,谢谢!这需要稍微检查一下......一件事是我不能使用.net 4(不能使用动态关键字)。
  • 然后你必须获取“CallMethod”方法的方法信息并调用它。可以通过 propertygrid 设置/读取属性。您也可以在 linqpad 中创建一个带有 propertygrid 的表单,这样您就可以在理解它的工作原理之前对其进行处理。
【解决方案2】:

我认为您可以在项目中添加一个实现ICustomTypeDescriptor 接口的新类。并使用此类的实例作为方法参数的包装器。

这是一个article,展示了如何通过实现ICustomTypeDescriptor 来自定义属性网格显示。

【讨论】:

  • 我已经接受了,但它仍然显示 0%。感谢您通知我。
  • 我的问题是如何在运行时创建一个具有每个方法参数属性的新对象?
  • 您可以使用ICustomTypeDescriptor 接口来控制pgrid 中显示的内容。您不需要创建具有这些属性的对象。这就是ICustomTypeDescriptor 存在的原因。请阅读文章。