Roslyn seems to ignore some metadata references - c#

During the preparation stage of some integration testing, I have to dynamically generate some assemblies with references to other assemblies and flush them to disk. The obvious choice for this task is Roslyn I guess.
Roslyn compilation completes successfully and emitted assemblies saved to disk. When I check the result using ILSPy, I see that some assembly references are not included.
Dummy class generation code:
public static string GenerateEmptyPublicClass([NotNull] string #namespace, [NotNull] string className)
{
if (#namespace == null) throw new ArgumentNullException(nameof(#namespace));
if (className == null) throw new ArgumentNullException(nameof(className));
var classDeclaration = SyntaxFactory.ClassDeclaration(className).AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));
var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(#namespace)).NormalizeWhitespace();
namespaceDeclaration = namespaceDeclaration.AddMembers(classDeclaration);
return namespaceDeclaration.NormalizeWhitespace().ToFullString();
}
Assembly preparation code:
blic static void GenerateAssembly([NotNull] this string sourceCode, [NotNull] string assemblyFilePath,
[NotNull] params string[] referencedAssemblyPaths)
{
if (sourceCode == null) throw new ArgumentNullException(nameof(sourceCode));
if (assemblyFilePath == null) throw new ArgumentNullException(nameof(assemblyFilePath));
var assemblyFileName = Path.GetFileName(assemblyFilePath);
var outputDirectory = Path.GetDirectoryName(assemblyFilePath);
Directory.CreateDirectory(outputDirectory);
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var referencedAssemblyMetadata =
referencedAssemblyPaths.Select(x => MetadataReference.CreateFromFile(x).WithProperties(new MetadataReferenceProperties()));
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(assemblyFileName, new[] {syntaxTree}, referencedAssemblyMetadata, compilationOptions);
using (var fs = File.Create(assemblyFilePath))
{
var emitResult = compilation.Emit(fs);
if (!emitResult.Success)
{
var failures = emitResult.Diagnostics.Where(x => x.IsWarningAsError || x.Severity == DiagnosticSeverity.Error);
var errorReport = failures.Select(x => $"{x.Id}: {x.GetMessage()}, {x.Location}");
throw new InvalidOperationException($"Failed to compile source code {sourceCode}. Report: {errorReport}");
}
fs.Flush();
}
}
For simplicity, I want to generate two assemblies:
B with the only dependency to netstandard.dll
A with references both to netstandard.dll and B
Here goes the code:
var emptyClassSourceCode = RoslynAssemblyGenerator.GenerateEmptyPublicClass("DummyNamespace", "DummyClass");
var standardAssemblyLocation = Path.Combine(Path.GetDirectoryName(Common.ExecutingAssemblyFullPath), "Resources", "netstandard.dll");
// A references B
var aPath = Path.Combine(AssemblyGenerationPath, "A.dll");
var bPath = Path.Combine(AssemblyGenerationPath, "B.dll");
emptyClassSourceCode.GenerateAssembly(bPath, standardAssemblyLocation);
emptyClassSourceCode.GenerateAssembly(aPath, bPath, standardAssemblyLocation);
B is generated as expected, but A does not refer to B:
Can't figure out what have I missed, why B is not referenced by A.

As PetSerAl mentioned in the comment, in order to reference assembly we need not only indicate assembly location but actually use its metadata.

Related

The name 'xxx' does not exist in the current context (are you missing a reference to assembly)

I have installed Microsoft.CodeAnalysis.CSharp & Microsoft.CodeAnalysis.CSharp.Scripting (Version 3.3.1) packages in the .Net Core 2.2 Console Application and also I have developed the codes below :
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(#"
public class MyGlobals
{
public int Age {get; set;} = 21;
}
");
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
};
var compilation = CSharpCompilation.Create("DynamicAssembly")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(syntaxTree)
.AddReferences(references);
Type globalsType = null;
Assembly assembly = null;
using (var memoryStream = new MemoryStream())
{
var compileResult = compilation.Emit(memoryStream);
assembly = Assembly.Load(memoryStream.GetBuffer());
if (compileResult.Success)
{
globalsType = assembly.GetType("MyGlobals");
}
}
var globals = Activator.CreateInstance(globalsType);
var validationResult = CSharpScript.EvaluateAsync<bool>("Age == 21", globals: globals);
The globals object is created but the expression is not evaluated and the following exception is throwed by CSharpScript :
The name 'Age' does not exist in the current context (are you missing a reference to assembly 'DynamicAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'?)'
Is there a setting I missed?
The reason why you get an error is the absence of reference to DynamicAssembly you just created. To fix this you may pass ScriptOptions to CSharpScript.EvaluateAsync<bool>() call. The following code runs well for me.
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(#"
public class MyGlobals
{
public int Age {get; set;} = 21;
}
");
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
};
var compilation = CSharpCompilation.Create("DynamicAssembly")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(syntaxTree)
.AddReferences(references);
Type globalsType = null;
Assembly assembly = null;
using (var memoryStream = new MemoryStream())
{
var compileResult = compilation.Emit(memoryStream);
var buffer = memoryStream.GetBuffer();
File.WriteAllBytes("DynamicAssembly.dll", buffer);
assembly = Assembly.LoadFile(Path.GetFullPath("DynamicAssembly.dll"));
if (compileResult.Success)
{
globalsType = assembly.GetType("MyGlobals");
}
}
var globals = Activator.CreateInstance(globalsType);
var options = ScriptOptions.Default.WithReferences("DynamicAssembly.dll");
var validationResult = CSharpScript.EvaluateAsync<bool>(
"Age == 21",
globals: globals,
options: options
);
Console.WriteLine(await validationResult);

Create control flow graph for c# code using the .Net compiler Roslyn

I can't find a way to construct a control flow graph for c# code using Roslyn.
I know there is a namespace in the Roslyn compiler called "Microsoft.CodeAnalysis.FlowAnalysis" that contains some classes to create a control flow graph but I don't know how to use it.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.flowanalysis?view=roslyn-dotnet
there is a class called ControlFlowGraph.cs but the problem i can't create an object or a subclass from this class.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.flowanalysis.controlflowgraph?view=roslyn-dotnet
please if anyone knows how to use this namespace to construct a control flow graph or if there is an example to use.
thank you
I have manage to create the CFG from a method node:
CSharpParseOptions options = CSharpParseOptions.Default
.WithFeatures(new[] { new KeyValuePair<string, string>("flow-analysis", "")
});
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
Solution solution = workspace.OpenSolutionAsync(solutionUrl).Result; // path to your SLN file
ProjectDependencyGraph projectGraph = solution.GetProjectDependencyGraph();
Dictionary<string, Stream> assemblies = new Dictionary<string, Stream>();
var projects = projectGraph.GetTopologicallySortedProjects().ToDictionary(
p => p,
p => solution.GetProject(p).Name);
var bllProjectId = projects.First(p => p.Value == "<your project name>").Key; // choose project for analysis
var projectId = bllProjectId;
solution = solution.WithProjectParseOptions(projectId, options);
Compilation compilation = solution.GetProject(projectId).GetCompilationAsync().Result;
if (compilation != null && !string.IsNullOrEmpty(compilation.AssemblyName))
{
var syntaxTree = compilation.SyntaxTrees.First();
// get syntax nodes for methods
var methodNodes = from methodDeclaration in syntaxTree.GetRoot().DescendantNodes()
.Where(x => x is MethodDeclarationSyntax)
select methodDeclaration;
foreach (MethodDeclarationSyntax node in methodNodes)
{
var model = compilation.GetSemanticModel(node.SyntaxTree);
node.Identifier.ToString().Dump();
if (node.SyntaxTree.Options.Features.Any())
{
var graph = ControlFlowGraph.Create(node, model); // CFG is here
}
else
{
// "No features".Dump();
}
}
}
The next step will be anaylysis of the CFG ...
Karel
Depending on the Karel's answer and comment this is how to create a Control Flow Graph without errors:
var source = #"
class C
{
int M(int x)
{
x = 0;
int y = x * 3;
return y;
}
}";
CSharpParseOptions options = CSharpParseOptions.Default
.WithFeatures(new[] { new KeyValuePair<string, string>("flow-analysis", "")});
var tree = CSharpSyntaxTree.ParseText(source, options);
var compilation = CSharpCompilation.Create("c", new[] { tree });
var model = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
var methodBodySyntax = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BaseMethodDeclarationSyntax>().Last();
var cfgFromSyntax = ControlFlowGraph.Create(methodBodySyntax, model);

Compiling a Syntax Tree using Roslyn

I'm trying to use Roslyn to generate and compile a runtime library of simple objects containing get/set properties.
However, for some reason, compiling the assembly fails with error of adding the Linq namespace (error CS0246: The type or namespace name 'System.Linq' could not be found (are you missing a using directive or an assembly reference?)}).
I've tried manipulating the generated tree in a number of ways and compiling each, but still compilation fails.
The only way in which compilation succeeds is if the tree is parsed to a string, then parsed back to a syntax tree and then compiled.
The code below does the following:
Build a simple syntax tree containing compilation unit, usings, namespace, class and property.
Try to compile the tree (fails)
Generate new Syntax Tree with C#6 option and compile (fails)
Format Syntax Tree and compile (fails)
Serialize tree to string, then use SyntaxFactory.ParseSyntaxTree and compile the generated tree (success)
The code:
private static readonly CSharpCompilationOptions DefaultCompilationOptions =
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithOverflowChecks(true)
.WithPlatform(Platform.X86)
.WithOptimizationLevel(OptimizationLevel.Release)
.WithUsings(DefaultNamespaces);
private static readonly IEnumerable<string> DefaultNamespaces =
new[]
{
"System",
"System.IO",
"System.Net",
"System.Linq",
"System.Text",
"System.Text.RegularExpressions"
};
private static readonly IEnumerable<MetadataReference> DefaultReferences =
new[]
{
MetadataReference.CreateFromFile(typeof (object).Assembly.Location),
MetadataReference.CreateFromFile(typeof (System.Linq.Enumerable).Assembly.Location),
MetadataReference.CreateFromFile(typeof (System.GenericUriParser).Assembly.Location),
MetadataReference.CreateFromFile(typeof (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.Location)
};
static void Main(string[] args)
{
MakeAssembly();
Console.ReadLine();
}
private static void MakeAssembly()
{
//Compilation Unit and Usings
CompilationUnitSyntax cu = SyntaxFactory.CompilationUnit()
.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(System.Linq.Enumerable).Namespace)))
;
// NameSpace
NamespaceDeclarationSyntax ns = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName("Roslyn"));
// Class
ClassDeclarationSyntax classNode = SyntaxFactory.ClassDeclaration("MyClass")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
;
// Property
classNode= classNode.AddMembers(
SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName("Int32"), "MyProperty")
.AddAccessorListAccessors(
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))).
AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)));
ns = ns.AddMembers(classNode);
cu = cu.AddMembers(ns);
// Try To Compile Syntax Tree root
var root = cu.SyntaxTree.GetRoot();
var st = root.SyntaxTree;
var assembly = CompileAndLoad(st);
if (assembly != null)
{
Console.WriteLine("Success compile syntax tree root");
return;
}
else
Console.WriteLine("failed to compile syntax tree root");
// Try to compile new syntax tree
var stNew = SyntaxFactory.SyntaxTree(cu, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
assembly = CompileAndLoad(stNew);
if (assembly != null)
{
Console.WriteLine("Success compile new syntax tree");
return;
}
else
Console.WriteLine("failed to compile new syntax tree");
// Try to format node
AdhocWorkspace cw = new AdhocWorkspace();
OptionSet options = cw.Options;
options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, false);
options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInTypes, false);
SyntaxNode formattedNode = Formatter.Format(cu, cw, options);
var stFormat = SyntaxFactory.SyntaxTree(cu, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
assembly = CompileAndLoad(stFormat);
if (assembly != null)
{
Console.WriteLine("Success compile formatted syntax tree");
return;
}
else
Console.WriteLine("failed to compile formatted syntax tree");
// Try to serialize and parse
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
formattedNode.WriteTo(writer);
}
var treeAsString = sb.ToString();
var stParsed = SyntaxFactory.ParseSyntaxTree(treeAsString);
assembly = CompileAndLoad(stParsed);
if (assembly != null)
{
Console.WriteLine("Success compile parsed syntax tree");
return;
}
else
Console.WriteLine("failed to compile formatted syntax tree");
}
private static Assembly CompileAndLoad(SyntaxTree st)
{
var compilation
= CSharpCompilation.Create("TestRoslyn.dll", new SyntaxTree[] { st }, null, DefaultCompilationOptions);
compilation = compilation.WithReferences(DefaultReferences);
using (var stream = new MemoryStream())
{
EmitResult result = compilation.Emit(stream);
if (result.Success)
{
var assembly = Assembly.Load(stream.GetBuffer());
return assembly;
}
return null;
}
}
I fell into this trap with Roslyn also. The using directive is not just expressed as a string each part of the qualified name is a syntax node. You need to create your node like this
var qualifiedName= SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"),
SyntaxFactory.IdentifierName("Linq"));
var usingDirective = SyntaxFactory.UsingDirective(qualifedName);
I wrote a helper method to convert a string to the correct syntax node.
private UsingDirectiveSyntax CreateUsingDirective(string usingName)
{
NameSyntax qualifiedName = null;
foreach (var identifier in usingName.Split('.'))
{
var name = SyntaxFactory.IdentifierName(identifier);
if (qualifiedName != null)
{
qualifiedName = SyntaxFactory.QualifiedName(qualifiedName, name);
}
else
{
qualifiedName = name;
}
}
return SyntaxFactory.UsingDirective(qualifiedName);
}
You can use SyntaxFactory.ParseName which will handle parsing the string and then building the qualified name syntax node for your using directives:
var qualifiedName = SyntaxFactory.ParseName("System.Linq");
var usingDirective = SyntaxFactory.UsingDirective(qualifiedName);

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.

Using a PropertyGrid to input method parameters

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.

Categories