使用PropertyGrid输入方法参数

时间:2011-03-23 09:23:41

标签: c#

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

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

我想向用户提供使用PropertyGrid控件轻松输入被调用方法的参数的选项。

问题是 - PropertyGrid适用于Object,而不适用于方法。

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

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

这有什么简单的解决方案吗?

谢谢!

2 个答案:

答案 0 :(得分:2)

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

以下是article,其中介绍了如何通过实施ICustomTypeDescriptor来自定义属性网格。

答案 1 :(得分:2)

这就是我昨天写的。 它应该在LinqPad中运行,这是一个非常棒的免费工具来测试linq查询或代码片段。 (通过廉价升级来获得智能感知)

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

在LinqPad中,您可以使用Dump()扩展方法让它在结果窗口中显示您的对象。这很容易看出实际发生了什么。

所以,如果你想知道如何动态构造一个类型并调用它,这应该可以让你开始:

编辑:我完全忘记提到,您确实需要将这两个命名空间添加到查询中。您可以通过点击F4->其他命名空间导入并添加这些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;
}