Using a PropertyGrid to input method parameters - c#

I'd like to use a PropertyGrid to input method parameters.
I have some application that will dynamically load user's DLLs and invoke methods with specific signature (a known return type).
I'd like to present the user the option to input the arguments to the called method easily with a PropertyGrid control.
Problem is -- PropertyGrid works on an Object, and not on a method.
I'd like to somehow "transform" the method at runtime into an object with properties reflecting its arguments, passing the input values to the method when invoking it.
Offcourse i'd like to have type validation, etc (if provided by the PropertyGrid, dont remember right now).
Is there any easy solution for this?
Thanks!

Well here is what I've written yesterday.
It is meant to be run in LinqPad, which is an awesome free tool to test linq queries or code snippets. (With an inexpensive upgrade to get intellisense)
The code should tell you how to deal with different kind of parameters (ref, out) and whether you are calling an instance method or not. (flip the comments in Main to test an instance method)
In LinqPad, you can use the Dump() extension method to let it show your objects in the results window. this is handy to see what is actually happening.
So, if you want to know how to dynamically construct a type and invoke it, this should get you started:
EDIT: I totally forgot to mention, that you do need to add these 2 namespaces to the query. You do that by hitting F4->additional namespace imports and adding these 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;
}

I think you could add a new class to your project that implement the ICustomTypeDescriptor interface. And use the instance of this class as the wrapper of your method parameters.
Here is an article shows how to custom property grid display by implementing ICustomTypeDescriptor.

Related

How to dynamically pass multiple object instance to another class.

I need to pass object of one class as parameter in other class dynamically. I have following code which has to be customize.
ABC abc = new ABC();
abc.id = "E100";
abc.type = ABCType.BB_UNIQUE;
abc.typeSpecified = true;
ABC t = new ABC();
t.id = "I";
t.yellowkey = MarketSector.Equity;
t.yellowkeySpecified = true;
t.type = ABCType.t;
t.typeSpecified = true;
ABC abc2 = new ABC();
abc2.id = "GB";
abc2.type = ABCType.ISIN;
abc2.typeSpecified = true;
ABCs i = new ABCs();
i.abc = new abc[] { abc, abc2, t };
I was able to as follows To dynamically create the class object but i am not able to pass it to another class:
string value = "E100,I";
string[] id;
id = value.Split(',');
IDictionary<string, ABC> col = new Dictionary<string, ABC>();
foreach (string val in id)
{
col[val] = new ABC();
col[val].id = val;
}
ABCs i = new ABCs();
This part is where i am struggling
i.abc = new abc[] { abc, abc2, t };
How will i be able to pass dynamic object of IDictionary to i.abc?
Any help will be appreciated
Thanks
The IDictionary<TKey, TValue> interface has a Values property for every value contained in the IDictionary<TKey, TValue> which returns an ICollection<TValue>.
After including the System.Linq namespace, the ICollection<T> interface gets an extension method ToArray<T>() which converts the collection to an array of type T.
So the solution for your problem should be the following line:
i.abc = col.Values.ToArray();

Getting parameters of Func<T> variable

I have a rather complicated issue. I am trying to get a unique key from a method and its formal and actual parameters. The goal of the method, is to take a method call, and return a unique key based on 1) The name of the class and method and 2) The name and values of the parameters it is called with.
The method looks like this (sorry for all the details, but I can't find a sensible way to make the example smaller yet still explain my problem)
public class MethodKey
{
public static string GetKey<T>(Expression<Func<T>> method, params string[] paramMembers)
{
var keys = new Dictionary<string, string>();
string scope = null;
string prefix = null;
ParameterInfo[] formalParams = null;
object[] actual = null;
var methodCall = method.Body as MethodCallExpression;
if (methodCall != null)
{
scope = methodCall.Method.DeclaringType.FullName;
prefix = methodCall.Method.Name;
IEnumerable<Expression> actualParams = methodCall.Arguments;
actual = actualParams.Select(GetValueOfParameter<T>).ToArray();
formalParams = methodCall.Method.GetParameters();
}
else
{
// TODO: Check if the supplied expression is something that makes sense to evaluate as a method, e.g. MemberExpression (method.Body as MemberExpression)
var objectMember = Expression.Convert(method.Body, typeof (object));
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
var getter = getterLambda.Compile();
var m = getter();
var m2 = ((System.Delegate) m);
var delegateDeclaringType = m2.Method.DeclaringType;
var actualMethodDeclaringType = delegateDeclaringType.DeclaringType;
scope = actualMethodDeclaringType.FullName;
var ar = m2.Target;
formalParams = m2.Method.GetParameters();
//var m = (System.MulticastDelegate)((Expression.Lambda<Func<object>>(Expression.Convert(method.Body, typeof(object)))).Compile()())
//throw new ArgumentException("Caller is not a method", "method");
}
// null list of paramMembers should disregard all parameters when creating key.
if (paramMembers != null)
{
for (var i = 0; i < formalParams.Length; i++)
{
var par = formalParams[i];
// empty list of paramMembers should be treated as using all parameters
if (paramMembers.Length == 0 || paramMembers.Contains(par.Name))
{
var value = actual[i];
keys.Add(par.Name, value.ToString());
}
}
if (paramMembers.Length != 0 && keys.Count != paramMembers.Length)
{
var notFound = paramMembers.Where(x => !keys.ContainsKey(x));
var notFoundString = string.Join(", ", notFound);
throw new ArgumentException("Unable to find the following parameters in supplied method: " + notFoundString, "paramMembers");
}
}
return scope + "¤" + prefix + "¤" + Flatten(keys);
}
private static object GetValueOfParameter<T>(Expression parameter)
{
LambdaExpression lambda = Expression.Lambda(parameter);
var compiledExpression = lambda.Compile();
var value = compiledExpression.DynamicInvoke();
return value;
}
}
Then, I have the following test, which works OK:
[Test]
public void GetKey_From_Expression_Returns_Expected_Scope()
{
const string expectedScope = "MethodNameTests.DummyObject";
var expected = expectedScope + "¤" + "SayHello" + "¤" + MethodKey.Flatten(new Dictionary<string, string>() { { "name", "Jens" } });
var dummy = new DummyObject();
var actual = MethodKey.GetKey(() => dummy.SayHello("Jens"), "name");
Assert.That(actual, Is.Not.Null);
Assert.That(actual, Is.EqualTo(expected));
}
However, if I put the () => dummy.SayHello("Jens") call in a variable, the call fails. Because I then no longer get a MethodCallExpression in my GetKey method, but a FieldExpression (subclass of MemberExpression. The test is:
[Test]
public void GetKey_Works_With_func_variable()
{
const string expectedScope = "MethodNameTests.DummyObject";
var expected = expectedScope + "¤" + "SayHello" + "¤" + MethodKey.Flatten(new Dictionary<string, string>() { { "name", "Jens" } });
var dummy = new DummyObject();
Func<string> indirection = (() => dummy.SayHello("Jens"));
// This fails. I would like to do the following, but the compiler
// doesn't agree :)
// var actual = MethodKey.GetKey(indirection, "name");
var actual = MethodKey.GetKey(() => indirection, "name");
Assert.That(actual, Is.Not.Null);
Assert.That(actual, Is.EqualTo(expected));
}
The Dummy class SayHello method definitions are trivial:
public class DummyObject
{
public string SayHello(string name)
{
return "Hello " + name;
}
public string Meet(string person1, string person2 )
{
return person1 + " met " + person2;
}
}
I have two questions:
Is there any way to send the variable indirection to MethodKey.GetKey, and get it as a MethodCallExpression type?
If not, how can I get the name and value of the method supplied if I get a MemberExpression instead? I have tried a few bits in the "else" part of the code, but haven't succeeded.
Any help is appreciated.
Thanks in advance, and sorry for the long post.
The problem is you are putting it into the wrong type of variable. Your method expects Expression<Func<T>> and you are using a variable of type Func<string> to store it. The following should fix your problem:
Expression<Func<string>> foo = () => dummy.SayHello("Jens");
var actual = MethodKey.GetKey<string>(foo, "name");
converting a .net Func<T> to a .net Expression<Func<T>> discusses the differences between a Func and an Expression<Func> and converting between the two and at a glance it says don't. The compiler makes them into totally different things. So make it the right thing at compile time and it should work fine.
If this isn't an option then possibly an overload that takes a Func instead of an Expression might work for you.
Note that in both cases I would pass the variable directly rather than trying to make it into a new expression in your call.

Best replacement for Reflections in C# .NET 4.0

I am faced with following code for retrieving data through reflections:
public object GetValue(object source)
{
if (Member == null) return Argument;
try
{
object[] argList = (Argument == null ? null : new object[] { Argument });
if (Member is PropertyInfo) return ((PropertyInfo)Member).GetValue(source, argList);
if (Member is MethodInfo) return ((MethodInfo)Member).Invoke(source, argList);
if (Member is FieldInfo) return ((FieldInfo)Member).GetValue(source);
throw new Exception("Unknown member type: " + Member);
}
catch (Exception ex)
{
throw new Exception("Could not get value " + Member.Name + " from " + source + " with " + Argument, ex);
}
}
This is however very slow, and I am thinking of replacing the reflections with something faster, maybe delegates? However, I am not sure that delegates is the best choice or how to implement it in this case. I am running .NET 4.0. Any suggestions are appreciated!
This is a little faster
static Func<object, object[], object> BuildCaller(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object));
var pars = Expression.Parameter(typeof(object[]));
var pars2 = method.GetParameters();
var casted = new Expression[pars2.Length];
for (int i = 0; i < pars2.Length; i++)
{
casted[i] = Expression.Convert(Expression.ArrayAccess(pars, Expression.Constant(i)), pars2[i].ParameterType);
}
var call = Expression.Call(Expression.Convert(obj, method.DeclaringType), method, casted);
var cast = Expression.Convert(call, typeof(object));
var lamdba = Expression.Lambda<Func<object, object[], object>>(cast, obj, pars);
return lamdba.Compile();
}
static Func<object, object[], object> BuildCaller(FieldInfo field)
{
var obj = Expression.Parameter(typeof(object));
var pars = Expression.Parameter(typeof(object[]));
var call = Expression.Field(Expression.Convert(obj, field.DeclaringType), field);
var cast = Expression.Convert(call, typeof(object));
var lamdba = Expression.Lambda<Func<object, object[], object>>(cast, obj, pars);
return lamdba.Compile();
}
static Func<object, object[], object> BuildCaller(PropertyInfo property)
{
var obj = Expression.Parameter(typeof(object));
var pars = Expression.Parameter(typeof(object[]));
var pars2 = property.GetIndexParameters();
var casted = new Expression[pars2.Length];
for (int i = 0; i < pars2.Length; i++)
{
casted[i] = Expression.Convert(Expression.ArrayAccess(pars, Expression.Constant(i)), pars2[i].ParameterType);
}
var call = Expression.Property(Expression.Convert(obj, property.DeclaringType), property, casted);
var cast = Expression.Convert(call, typeof(object));
var lamdba = Expression.Lambda<Func<object, object[], object>>(cast, obj, pars);
return lamdba.Compile();
}
I create a delegate (through Expressions) that precaches the method. If Arguments are "fixed" you could go a step further and precache even their conversion (as is the conversion is done each time the method is called)
This is an example for methods. For properties and fields it's similar (for fields it's even easier).
And an example of use
var fun = BuildCaller(typeof(MyClass).GetMethod("MyMethod"));
var mc = new MyClass();
fun(mc, new object[] { 1, 2.0 });
You must cache fun together with your Method

Execute code lines from a text file in C#

I have a text file looks like:
AssembleComponent Motor = new AssembleComponent;
AssembleComponent Shaft = new AssembleComponent;
......
Motor.cost = 100;
Motor.quantity = 100;
Shaft.cost = 10;
Shaft.quantity = 100;
......
I wish to execute these code lines in C#, so that I will have these Motor.cost, Motor.quantity, Shaft.cost, Shaft.quantity variables stored in the memory for later calculation.
What can I do to achieve this?
Store it as XML instead
<?xml version="1.0" encoding="UTF-8"?>
<Components>
<Component name="Motor" cost="100" quantity="100" />
<Component name="Shaft" cost="10" quantity="100" />
</Components>
Assuming that you have this definition
public class AssembleComponent
{
public decimal Cost { get; set; }
public int Quantity { get; set; }
}
Load it like this
var components = new Dictionary<string, AssembleComponent>();
XDocument doc = XDocument.Load(#"C:\Users\Oli\Desktop\components.xml");
foreach (XElement el in doc.Root.Descendants()) {
string name = el.Attribute("name").Value;
decimal cost = Decimal.Parse(el.Attribute("cost").Value);
int quantity = Int32.Parse(el.Attribute("quantity").Value);
components.Add(name, new AssembleComponent{
Cost = cost, Quantity = quantity
});
}
You can then access the components like this
AssembleComponent motor = components["Motor"];
AssembleComponent shaft = components["Shaft"];
Note: Creating the variable names dynamically by calling the compiler at runtime is not very useful since you need to know them at compile-time (or design-time if you prefer) to do something useful with them. Therefore, I added the components to a dictionary. This is a good way of creating "variables" dynamically.
You can use Microsoft.CSharp.CSharpCodeProvider to compile code on-the-fly.
Specifically, take a look at CompileAssemblyFromFile.
If it's just about data don't use a flat textfile but XML-instead.
You can deserialize the XML in to objects and perform the necessary actions on them.
Here's some code that I've used in the past, that does most of what you want though you may need to adapt it to your specific needs. In a nutshell, it does the following:
Create a temporary namespace and a public static method in that namespace.
Compile the code to an in-memory assembly.
Extract the compiled method and turn it into a delegate.
Execute the delegate.
At that point it's like executing a normal static method, so when you say you want the results in memory for later use, you'd have to figure out how that would work.
public void CompileAndExecute(string CodeBody)
{
// Create the compile unit
CodeCompileUnit ccu = CreateCode(CodeBody);
// Compile the code
CompilerParameters comp_params = new CompilerParameters();
comp_params.GenerateExecutable = false;
comp_params.GenerateInMemory = true;
comp_params.TreatWarningsAsErrors = true;
comp_results = code_provider.CompileAssemblyFromDom(comp_params, ccu);
// CHECK COMPILATION RESULTS
if (!comp_results.Errors.HasErrors)
{
Type output_class_type = comp_results.CompiledAssembly.GetType("TestNamespace.TestClass");
if (output_class_type != null)
{
MethodInfo compiled_method = output_class_type.GetMethod("TestMethod", BindingFlags.Static | BindingFlags.Public);
if (compiled_method != null)
{
Delgate created_delegate = Delegate.CreateDelegate(typeof(System.Windows.Forms.MethodInvoker), compiled_method);
if (created_delegate != null)
{
// Run the code
created_delegate.DynamicInvoke();
}
}
}
}
else
{
foreach (CompilerError error in comp_results.Errors)
{
// report the error
}
}
}
public CodeCompileUnit CreateCode(string CodeBody)
{
CodeNamespace code_namespace = new CodeNamespace("TestNamespace");
// add the class to the namespace, add using statements
CodeTypeDeclaration code_class = new CodeTypeDeclaration("TestClass");
code_namespace.Types.Add(code_class);
code_namespace.Imports.Add(new CodeNamespaceImport("System"));
// set function details
CodeMemberMethod method = new CodeMemberMethod();
method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
method.ReturnType = new CodeTypeReference(typeof(void));
method.Name = "TestMethod";
// add the user typed code
method.Statements.Add(new CodeSnippetExpression(CodeBody));
// add the method to the class
code_class.Members.Add(method);
// create a CodeCompileUnit to pass to our compiler
CodeCompileUnit ccu = new CodeCompileUnit();
ccu.Namespaces.Add(code_namespace);
return ccu;
}
You have two main options:
Expand the text until it becomes valid C# code, compile it and execute it
Parse it and execute it yourself (i.e. interpret it).
This can be done following these steps: CodeGeneration => InMemory Compilation to Exe ==> Execution.
You can design the construct similar to this:
public bool RunMain(string code)
{
const string CODE_NAMESPACE = "CompileOnFly";
const string CODE_CLASS = "Program";
const string CODE_METHOD = "Main";
try
{
var code_namespace = new CodeNamespace(CODE_NAMESPACE);
// add the class to the namespace, add using statements
var code_class = new CodeTypeDeclaration(CODE_CLASS);
code_namespace.Types.Add(code_class);
code_namespace.Imports.Add(new CodeNamespaceImport("System"));
// set function details
var method = new CodeMemberMethod();
method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
method.ReturnType = new CodeTypeReference(typeof(void));
method.Name = CODE_METHOD;
// add the user typed code
method.Statements.Add(new CodeSnippetExpression(code));
// add the method to the class
code_class.Members.Add(method);
// create a CodeCompileUnit to pass to our compiler
CodeCompileUnit code_compileUnit = new CodeCompileUnit();
code_compileUnit.Namespaces.Add(code_namespace);
var compilerParameters = new CompilerParameters();
compilerParameters.ReferencedAssemblies.Add("system.dll");
compilerParameters.GenerateExecutable = true;
compilerParameters.GenerateInMemory = true;
compilerParameters.TreatWarningsAsErrors = true;
var code_provider = CodeDomProvider.CreateProvider("CSharp");
var comp_results = code_provider.CompileAssemblyFromDom(compilerParameters, code_compileUnit);
if (comp_results.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in comp_results.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
//Get assembly, type and the Main method:
Assembly assembly = comp_results.CompiledAssembly;
Type program = assembly.GetType($"{CODE_NAMESPACE}.{CODE_CLASS}");
MethodInfo main = program.GetMethod(CODE_METHOD);
//runtit
main.Invoke(null, null);
return true;
}
catch(Exception compileException)
{
Console.Write(compileException.ToString());
return false;
}
}
In the code above we are actually creating a simple console Program.Main() as
namespace CompileOnFly
{
internal class Program
{
static void Main()
{
//<your code here>
}
}
}
in memory then compiling it as executable in Memory and executing it. But the Main() body //<your code here> is added dynamically with the parameter code to the method.
So If you have a script in the text file script.txt as this:
Console.Write("Write your name: ");
var name = Console.ReadLine();
Console.WriteLine("Happy new year 2023 " + name);
You can simply read all the text and send it as parameter to it:
var code = File.ReadAllText(#"script.txt");
RunMain(code);
To run the statements in the script.txt file.

how to execute string path on dynamic type?

Is it possible to execute string path on dynamic type?
For example having dynamic type we can write
dynamic d = myObj;
var v = d.MyMethod(1,"text").SomeProperty.Name
Now imagine I have string path
string path = "MyMethod(1,\"text\").SomeProperty.Name";
var v = d. //How to applay string path to this type?
I have solution using extension method and reflection, you need to optimize and test for different scenario.
EDIT
Still dirty code, but supports overloaded method now. I will try to do code clean up and use regex for effective and cleaner solution
You can specify data types for parameters now to the eval method.
string epath = "GetName(System_String: ding dong, System_Int32:1).name";
MyClass cls = new MyClass();
var v = cls.Eval(epath);
Note underscore in type names. This should work without mentioning datatypes if method are not overloaded. Current restriction, you cannot use colon or comma inside string parameter value. :(
Call like var v = d.Execute(path)
public static object Eval(this object instance, string path)
{
string[] cmd = path.Split('.');
string subString = cmd[0];
object returnValue = null;
Type t = instance.GetType();
if (subString.Contains("("))
{
string[] paramString = subString.Split('(');
string[] parameters = paramString[1].Replace(")", "").Split(new Char[]{','},StringSplitOptions.RemoveEmptyEntries);
bool hasNoParams = parameters.Length == 0;
List<Type> typeArray = null;
if (hasNoParams) typeArray = new List<Type>();
foreach (string parameter in parameters)
{
if (parameter.Contains(":"))
{
if (typeArray == null) typeArray = new List<Type>();
string[] typeValue = parameter.Split(':');
Type paramType = Type.GetType(typeValue[0].Replace('_','.'));
typeArray.Add(paramType);
}
}
MethodInfo info = null;
if (typeArray == null)
info = t.GetMethod(paramString[0]);
else
info = t.GetMethod(paramString[0], typeArray.ToArray());
ParameterInfo[] pInfo = info.GetParameters();
List<object> paramList = new List<object>();
for (int i = 0; i < pInfo.Length; i++)
{
string currentParam = parameters[i];
if (currentParam.Contains(":"))
{
currentParam = currentParam.Split(':')[1];
}
ParameterInfo pram = pInfo[i];
Type pType = pram.ParameterType;
object obj = Convert.ChangeType(currentParam, pType);
paramList.Add(obj);
}
if (info == null) returnValue = null;
else
returnValue = info.Invoke(instance, paramList.ToArray());
}
else
{
PropertyInfo pi = t.GetProperty(subString);
if (pi == null) returnValue = null;
else
returnValue = pi.GetValue(instance, null);
}
if (returnValue == null || cmd.Length == 1)
return returnValue;
else
{
returnValue = returnValue.Eval(path.Replace(cmd[0] + ".", ""));
}
return returnValue;
}
It seems you would need an eval function and C# has none but if this third party C# eval implementation can handle dynamic it might solve your problem.

Categories