I would like to store C# functions in DLL files and later call that function with parameters. So far i've been able to store the function in a DLL file with the following code:
var codeProvider = new CSharpCodeProvider();
var icc = codeProvider.CreateCompiler();
var parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "Sum.dll";
icc.CompileAssemblyFromSource(parameters, code);
The function in the DLL file (value of variable code above is):
public class Function : IFunc
{
public string ID
{
get { return ""Sum""; }
}
public string Name
{
get { return ""Sum""; }
}
public string Description
{
get { return ""Return the sum of the values specified in args""; }
}
public ResultSet Execute(params string[] args)
{
var sum = 0;
foreach(var arg in args)
{
var rslt = 0;
if(int.TryParse(arg, out rslt))
{
sum += rslt;
}
}
ResultSet rtn = new ResultSet();
rtn.Result = sum.ToString();
rtn.Type = ""int"";
return rtn;
}
}
I've used Assembly.LoadFile to load the DLL and used reflection to fetch the class containing the function. I also have 2 identical interface, one in my project and one in the DLL file:
public interface IFunc
{
string ID { get; }
string Name { get; }
string Description { get; }
string Execute(params string[] args);
}
To be able to call the function i use:
public static IFunc CreateSumFunction()
{
var dll = Assembly.LoadFile(#"...\Sum.dll");
var func = dll.GetType("Function"); // Class containing the function
var instance = Activator.CreateInstance(func);
return (IFunc)instance; // <--- CRASH
}
Part of the exception:
System.Windows.Markup.XamlParseException was unhandled
Message='The invocation of the constructor on type 'GenericCoder.MainWindow' that matches the specified binding constraints threw an exception.' Line number '3' and line position '9'.
Is there a way to resolve this, or maybe a complete new way of doing it?
Add your library to references of your project. Then you're able to use the functions without the need of reflections.
Related
I wanted to create a safe class that checks the values provided to it for validity, so that later when working with an instance, I have to write fewer checks.
public class FerryArguments
{
private readonly string? _sourceDirectory;
public string SourceDirectory { get => _sourceDirectory!; init { if (!Directory.Exists(value)) { throw new ArgumentException("Source directory does not exist"); } _sourceDirectory = value; } }
public Uri ServiceUrl { get; init; }
public FerryArguments(string srouceDirectory, Uri serviceUrl)
{
SourceDirectory = srouceDirectory;
ServiceUrl = serviceUrl;
}
}
When instantiating FerryArguments, it makes sure the directory exists.
Using this in a console app for example, I would read the command line arguments, check if everything is provided, and then construct FerryArguments.
I can't figure out how to parse and return in a single call, because if something is wrong with the command line arguments, I can't return a valid FerryArguments instance:
FerryArguments GetFerryArgs(string[] args)
{
// no way to fail cleanly
}
Same thing if I do a "TryParse"-style approach:
bool TryGetFerryArgs(string[] args, out FerryArguments ferryArgs)
{
var arglist = args.ToList();
var dirIndex = arglist.IndexOf("-d");
var webIndex = arglist.IndexOf("-s");
if (dirIndex == -1)
{
ferryArgs = // can't assign anything
return false;
}
}
In the first version (GetFerryArgs) you might just throw an exception as that's what is going to happen anyway if the arguments are badly formed. Another option would be to return a nullable value and check for null on the call site.
In the second version (TryGetFerryArgs) the out parameter should be nullable:
bool TryGetFerryArgs(string[] args, [NotNullWhen(true)] out FerryArguments? ferryArgs)
{
var arglist = args.ToList();
var dirIndex = arglist.IndexOf("-d");
var webIndex = arglist.IndexOf("-s");
if (dirIndex == -1)
{
ferryArgs = null;
return false;
}
...
}
The NotNullWhen attribute tells the compiler that the null-state of the ferryArgs parameter depends on the return value of the method.
I have a project that stores data of "custom" types and each of these types is represented as a class. I am able to collect these types from assembly and create instances of these when I need to in run time:
var type = typeof(IDataPointListableCore);
var availableTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(w => w.IsAbstract == false)
.Where(x => type.IsAssignableFrom(x));
...
IDataPointListableCore c = (IDataPointListableCore)Activator.CreateInstance(item, "");
It worked fine but an obvious disadvantage of this approach is that if I want to add a new type I have to rebuild and deploy the application. So I decided to externalize these classes into .ddl assemblies that I would be loading from a folder on application start.
A sample type would look like this:
namespace DataPoints
{
public class DataPointRAG : IDataPoint<string>
{
private enum AcceptedValues { none, red, amber, green };
public string ParsedValue { get; private set; } = null;
public string Name { get; } = "nRAG";
public string Description { get; } = "One of the four RAG indicators: None, Red, Amber, Green";
public string Value { get; private set; }
public DataPointRAG(string _v)
{
Value = _v;
}
public Type GetExpectedValueType()
{
return typeof(string);
}
public string GetParsedValue()
{
return ParsedValue;
}
public ValidationResult Validate()
{
if (Enum.IsDefined(typeof(AcceptedValues), Value.ToLower()))
{
ParsedValue = Value.ToLower();
return new ValidationResult(true, "OK");
}
return new ValidationResult(false,
string.Format("Given value ({0}) does not correspond to any of the accepted values {1} for this data point type",
Value,
Enum.GetValues(typeof(AcceptedValues)).ToString()));
}
}
}
And it implements two interfaces (I reference a project with these):
namespace DataPoints
{
public interface IDataPointListable
{
string Name { get; }
string Description { get; }
string ParsedValue { get; }
string Value { get; }
ValidationResult Validate();
}
}
namespace DataPoints
{
public interface IDataPoint<T> : IDataPointListable
{
System.Type GetExpectedValueType();
T GetParsedValue();
}
}
The problem is that when I load the assembly and try to instantiate this class, it throws an InvalidCastException: can't cast DataPoints.DataPointRAG to IDataPointListable
public static List<Assembly> Asses = new List<Assembly>();
...
public static void Main(string[] args)
{
var _loggerFactory = new LoggerFactory()
.AddConsole(LogLevel.Debug)
.AddDebug();
var _logger = _loggerFactory.CreateLogger("Startup");
string[] files = Directory.GetFiles(DATAPOINTSFOLDER, "*.dll");
foreach (string f in files)
{
Assembly DataPointsAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(f);
Asses.Add(DataPointsAssembly);
}
foreach (Type _assType in Asses.SelectMany(x => x.GetTypes()))
{
_logger.LogDebug("Found {0} in {1}", _assType, _assType.FullName);
}
foreach (Type _assType in Asses.SelectMany(x => x.GetTypes()).Where(x=>typeof(IDataPointListable).IsAssignableFrom(x)))
{
_logger.LogDebug("Found matching assemblies");
var point = (IDataPointListable)Activator.CreateInstance(_assType, "");
_logger.LogDebug("{0} successfully initiated", point.Name);
}
BuildWebHost(args).Run();
}
It does not recognize any of the found types as Assignable to IDataPointListable:
log:
[40m[37mdbug[39m[22m[49m: Startup[0]
Found DataPoints.DataPointRAG in DataPoints.DataPointRAG
[40m[37mdbug[39m[22m[49m: Startup[0]
Found DataPoints.ValidationResult in DataPoints.ValidationResult
[40m[37mdbug[39m[22m[49m: Startup[0]
Found DataPoints.IDataPoint`1[T] in DataPoints.IDataPoint`1
[40m[37mdbug[39m[22m[49m: Startup[0]
Found DataPoints.IDataPointListable in DataPoints.IDataPointListable
[40m[37mdbug[39m[22m[49m: Startup[0]
Found DataPoints.DataPointRAG+AcceptedValues in DataPoints.DataPointRAG+AcceptedValues
but if I try something like this (just to load the type based on the class name since I know it implements the proper interface) - I will get the InvalidCastException:
string[] files = Directory.GetFiles(DATAPOINTSFOLDER, "*.dll");
foreach (string f in files)
{
Assembly DataPointsAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(f);
foreach(Type _t in DataPointsAssembly.GetTypes()){
_logger.LogDebug("Type {0} in Assembly {1}", _t, DataPointsAssembly.FullName);
if (_t.ToString() == "DataPoints.DataPointRAG"){
var x = (IDataPointListable)Activator.CreateInstance(_t, "");
_logger.LogDebug("Activated class of type {0}", x.GetType());
}
}
if I don't cast to IDataPointListable then it will generate an instance of the DataPointRAG and the public class fields will be in there but it will be as if the class implemented no interfaces at all.
Is there something I am missing from how things work when loaded from external assembly?
The structure of my project is:
Core->References DataPointTemplate project (DataPoints namespace)
DataPointTemplate project provides the two interfaces and
ValidationMessage class Data PointRAG project->References DatapointTemplate project, but same namespace
While am executing,code getting an error i don't know where i have to make change to implement interface.
below is my full code that i tried with console application
Error 1 'ConsoleApplication16.Temp' does not implement interface member 'System.IComparable<ConsoleApplication16.Temp>.CompareTo(ConsoleApplication16.Temp)' D:\nnn\ConsoleApplication16\ConsoleApplication16\Program.cs 32 18 ConsoleApplication16
Below is my code
class Program
{
static void Main()
{
string []files=Directory.GetFiles("C:\\WINDOWS","*.*",SearchOption.AllDirectories);
Console.WriteLine(files.Count());
//Get Maximum
var max = (from fileName in files
let info = new FileInfo(fileName)
orderby info.Length descending
select new { FileName = info.Name, Size = info.Length })
.Take(1);
Console.WriteLine("Using Take : {0}",max.ElementAt(0));
//With anonymous Type we have to indicate what to get the max of
var max2 = (from fileName in files
let info = new FileInfo(fileName)
select
new Temp{ FileName = info.Name, Size = info.Length })
.Max(s => s.Size);
Console.WriteLine("Using Max:{0}", max2);
Console.ReadLine();
}
}
public class Temp : IComparable<Temp> //error is here
{
public string FileName { set; get; }
public long Size { set; get; }
public int compareTo(Temp o)
{
return Size.CompareTo(o.Size);
}
public override string ToString()
{
return string.Format("FileName:{0},Size:{1}", FileName, Size);
}
}
C# is case sensitive, it is CompareTo instead of compareTo
public int CompareTo(Temp o)
{
return Size.CompareTo(o.Size);
}
However, i don't know how this compiler error is related to the rest of the code since CompareTo is never used in your LINQ query.
Let us say I create a type dynamically using CSharpCodeProvider and choose NOT to persist the results. The assembly that is generated is existing only in memory.
Let us say I Create two types in two different in-memory assemblies:
Assembly1:
public class DynamicTypeA { }
Assembly2:
public class DynamicTypeB
{
public DynamicTypeA MyProperty { get; set; }
}
As you can see the second type has a property of the first type.
Cool. Now I want to explore DynamicTypeB using reflection:
foreach (PropertyInfo pi in typeof(DynamicTypeB).GetProperties())
{
Console.WriteLine(pi.PropertyType.Name);
}
It turns out that PropertyInfo.PropertyType fails when the assembly is not located on disk !!!
This is true for MemberInfo and for all other type investigation constructs.
As we all know lots of .Net APIs is using type investigation on the backend and they would fail when the investigated type happens to live in an in-memory assembly. For Example Expression.Bind takes a MemberInfo as the first parameter and is using it to validate that the type of the expression provided in the second parameter matches the type of the member. When this type happens to be in an in memory assembly Expression.Bind fails.
Can anyone think of a solution?
Creating types dynamically and writing them do disk pollutes the running environment and that is bad, yet without reflection these types are worthless.
Thanks
Manu
It turns out that PropertyInfo.PropertyType fails when the assembly is not located on disk
Are you sure? Take a look:
static void Main( string[] args )
{
string code = #"
namespace foo {
public class DynamicTypeA { }
public class DynamicTypeB {
public DynamicTypeA MyProperty { get; set; }
}
}
";
CSharpCodeProvider csp = new CSharpCodeProvider();
CompilerParameters p = new CompilerParameters();
p.GenerateInMemory = true;
var results = csp.CompileAssemblyFromSource( p, code );
foreach ( Type type in results.CompiledAssembly.GetTypes() )
{
Console.WriteLine( type.Name );
foreach ( PropertyInfo pi in type.GetProperties() )
{
Console.WriteLine( "\t{0}", pi.PropertyType.Name );
}
}
Console.ReadLine();
}
This uses your snippet and works like a charm.
Moving the loop to the inside of the dynamic code doesn't change much, it still works:
string code = #"
using System;
using System.Reflection;
namespace foo {
public class DynamicTypeA { }
public class DynamicTypeB {
public DynamicTypeA MyProperty { get; set; }
}
public class DynamicTypeC {
public void Foo() {
foreach ( PropertyInfo pi in typeof(DynamicTypeB).GetProperties() )
{
Console.WriteLine( pi.PropertyType.Name );
}
}
}
}
";
CSharpCodeProvider csp = new CSharpCodeProvider();
CompilerParameters p = new CompilerParameters();
p.GenerateInMemory = true;
var results = csp.CompileAssemblyFromSource( p, code );
var c = results.CompiledAssembly.CreateInstance( "foo.DynamicTypeC" );
var typeC = c.GetType();
typeC.InvokeMember( "Foo", BindingFlags.InvokeMethod |
BindingFlags.Public | BindingFlags.Instance, null, c, null );
If for some reason you have issues here, you are definitely doing something more complicated.
I found the problem:
I need to load the dynamically compiled assemblies into the current AppDomain, only then I will be able to retrieve any info by reflection.
I would like to thank Sam Alavi for explaining this to me.
Here is the code with the necessary fix:
public class HomeController : Controller
{
public Assembly AssemblyA { get; set; }
public Assembly AssemblyB { get; set; }
public ActionResult Index()
{
var provider = new CSharpCodeProvider();
var parametersA = new CompilerParameters();
parametersA.GenerateInMemory = true;
parametersA.OutputAssembly = "dynamicA.dll";
var code1 = #"namespace DynamicA { public class DynamicClassA { } }";
var result1 = provider.CompileAssemblyFromSource(parametersA, code1);
this.AssemblyA = result1.CompiledAssembly;
var parametersB = new CompilerParameters();
parametersA.GenerateInMemory = true;
parametersB.ReferencedAssemblies.Add("dynamicA.dll");
parametersB.OutputAssembly = "dynamicB.dll";
var code2 = #"using DynamicA; namespace DynamicB { public class DynamicB { public DynamicClassA MyProperty { get; set; } } }";
var results2 = provider.CompileAssemblyFromSource(parametersB, code2);
this.AssemblyB = results2.CompiledAssembly;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
if (e.Name.Contains("dynamicA"))
return this.AssemblyA;
if (e.Name.Contains("dynamicB"))
return this.AssemblyB;
return null;
};
AppDomain.CurrentDomain.Load(this.AssemblyA.FullName);
AppDomain.CurrentDomain.Load(this.AssemblyB.FullName);
var t = results2.CompiledAssembly.DefinedTypes.First();
var pi = t.GetProperty("MyProperty");
var res = pi.PropertyType.Name;
return View(res);
}
}
I'm trying to write a function in C# that takes in a string containing typescript code and returns a string containing JavaScript code. Is there a library function for this?
You can use Process to invoke the compiler, specify --out file.js to a temporary folder and read the contents of the compiled file.
I made a little app to do that:
Usage
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
To get the JS string
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
Full source with example and comments:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
// compiles a TS file
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
// if no errors were found, read the contents of the compile file
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
}
catch (InvalidTypeScriptFileException ex)
{
// there was a compiler error, show the compiler output
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
public static class TypeScriptCompiler
{
// helper class to add parameters to the compiler
public class Options
{
private static Options #default;
public static Options Default
{
get
{
if (#default == null)
#default = new Options();
return #default;
}
}
public enum Version
{
ES5,
ES3,
}
public bool EmitComments { get; set; }
public bool GenerateDeclaration { get; set; }
public bool GenerateSourceMaps { get; set; }
public string OutPath { get; set; }
public Version TargetVersion { get; set; }
public Options() { }
public Options(bool emitComments = false
, bool generateDeclaration = false
, bool generateSourceMaps = false
, string outPath = null
, Version targetVersion = Version.ES5)
{
EmitComments = emitComments;
GenerateDeclaration = generateDeclaration;
GenerateSourceMaps = generateSourceMaps;
OutPath = outPath;
TargetVersion = targetVersion;
}
}
public static void Compile(string tsPath, Options options = null)
{
if (options == null)
options = Options.Default;
var d = new Dictionary<string,string>();
if (options.EmitComments)
d.Add("-c", null);
if (options.GenerateDeclaration)
d.Add("-d", null);
if (options.GenerateSourceMaps)
d.Add("--sourcemap", null);
if (!String.IsNullOrEmpty(options.OutPath))
d.Add("--out", options.OutPath);
d.Add("--target", options.TargetVersion.ToString());
// this will invoke `tsc` passing the TS path and other
// parameters defined in Options parameter
Process p = new Process();
ProcessStartInfo psi = new ProcessStartInfo("tsc", tsPath + " " + String.Join(" ", d.Select(o => o.Key + " " + o.Value)));
// run without showing console windows
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
// redirects the compiler error output, so we can read
// and display errors if any
psi.RedirectStandardError = true;
p.StartInfo = psi;
p.Start();
// reads the error output
var msg = p.StandardError.ReadToEnd();
// make sure it finished executing before proceeding
p.WaitForExit();
// if there were errors, throw an exception
if (!String.IsNullOrEmpty(msg))
throw new InvalidTypeScriptFileException(msg);
}
}
public class InvalidTypeScriptFileException : Exception
{
public InvalidTypeScriptFileException() : base()
{
}
public InvalidTypeScriptFileException(string message) : base(message)
{
}
}
}
Perhaps you could use a JavaScript interpreter like JavaScriptDotNet to run the typescript compiler tsc.js from C#.
Something like:
string tscJs = File.ReadAllText("tsc.js");
using (var context = new JavascriptContext())
{
// Some trivial typescript:
var typescriptSource = "window.alert('hello world!');";
context.SetParameter("typescriptSource", typescriptSource);
context.SetParameter("result", "");
// Build some js to execute:
string script = tscJs + #"
result = TypeScript.compile(""typescriptSource"")";
// Execute the js
context.Run(script);
// Retrieve the result (which should be the compiled JS)
var js = context.GetParameter("result");
Assert.AreEqual(typescriptSource, js);
}
Obviously that code would need some serious work. If this did turn out to be feasible, I'd certainly be interested in the result.
You'd also probably want to modify tsc so that it could operate on strings in memory rather than requiring file IO.
The TypeScript compiler file officially runs on either node.js or Windows Script Host - it is written in TypeScript itself (and transpiled to JavaScript). It requires a script host that can access the file system.
So essentially, you can run TypeScript from any language as long as you can wrap it in a script engine that supports the file system operations required.
If you wanted to compile TypeScript to JavaScript purely in C#, you would end up writing a C# clone of the compiler.