6

Can I build a class as shown below dynamically using reflection? There are no methods, just public variables, some have custom attributes.

Is the .Emit method required (from what I've seen, "Emit" looks a little challenging).

I'm using software from www.FileHelpers.net, and it requires a class. All my file definitions are in a database table, and I'd like to make everything more dynamic (i.e. no code changes when a new column appears in the file).

[FileHelpers.DelimitedRecord(",")]
public class FileRow
{
    [FileHelpers.FieldQuoted('"', QuoteMode.OptionalForBoth)] 
    public string Borrower_First_Name;
    [FileHelpers.FieldQuoted('"', QuoteMode.OptionalForBoth)] 
    public string Borrower_Last_Name;
    public string Borrower_Email;
}

Update 1: Based on Vlad's answer below I needed to reference DLL, here's how I did it:

    // need to reference the FileHelpers.dll from our own .exe directory 
    string diskFilenameFileHelpersDLL = 
        System.IO.Path.GetDirectoryName(
           System.Reflection.Assembly.GetExecutingAssembly().Location) + 
           @"\FileHelpers.dll";

Update 2: Also, after doing what Vlad suggested, this is how I call FileHelper and loop through the results. I'll probably transfer the data to a list.

    Assembly assembly = compiledResult.CompiledAssembly;

    // Simple Data Test 
    lineContents = "John,Doe,jd123456@yahoo.com";
    FileHelperEngine engine = new FileHelperEngine(assembly.GetType("FileRow"));
    // FileRow[] FileRowArray = (FileRow[])engine.ReadString(lineContents);
    Object[] FileRowArray = engine.ReadString(lineContents);
    Object myObject = FileRowArray[0];  // only 1 row of data in this example 

    // Get the type handle of a specified class.
    Type myType = assembly.GetType("FileRow");
    // Get the fields of the specified class.
    FieldInfo[] myField = myType.GetFields();

    Console.WriteLine("\nDisplaying fields values:\n");
    for (int i = 0; i < myField.Length; i++)
    {
        Object objTest = myField.GetValue(i);

        string tempName = myField[i].Name;
        Object objTempValue = myField[i].GetValue(myObject);
        string tempValue = System.Convert.ToString(objTempValue);

        Console.WriteLine("The value of {0} is: {1}",
                            tempName, tempValue);

    }
Marcos Meli
  • 3,338
  • 21
  • 29
NealWalters
  • 14,090
  • 34
  • 109
  • 199

2 Answers2

2

You can also use CodeDOM to generate your class dynamically. For more information visit

http://msdn.microsoft.com/en-us/library/ms404245.aspx

Ramesh
  • 12,212
  • 2
  • 48
  • 83
  • That looks easy; I have not even heard of it... It can do the attributes too I suppose? – NealWalters Feb 23 '12 at 14:04
  • I see it is also using Streamwriter to write to disk. Can I just create the class and use it in my code without writing to disk? – NealWalters Feb 23 '12 at 14:07
  • Yes we can using CustomAttributes property. – Ramesh Feb 23 '12 at 14:14
  • Yes, you can just create an assembly. Instead of provider.GenerateCodeFromCompileUnit use provider.CompileAssemblyFromDom method – Ramesh Feb 23 '12 at 14:16
  • I think this would get me to the same end as Vlad's answer above, but there seemed to be less of a learning curve for me there, building the C# code in text, and compiling it, vs having to learn the CodeDOM structure. – NealWalters Feb 24 '12 at 19:00
2

If you have your code stored in the database as string what you can do something like this to create an assembly:

The reason I commented out attributes because I don't have namespace for them. I am assuming you have namespace and you will need to add it to your code to compile.

Code works in LINQPad, so you can just copy and paste.

using System;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

void Main()
{
    StringBuilder dc = new StringBuilder(512);
    dc.Append("public class FileRow");
    dc.Append("{");
    //dc.Append("[FileHelpers.FieldQuoted('\"', QuoteMode.OptionalForBoth)]");
    dc.Append("public string Borrower_First_Name;");
    //dc.Append("[FileHelpers.FieldQuoted('\"', QuoteMode.OptionalForBoth)]");
    dc.Append("public string Borrower_Last_Name;");
    dc.Append("public string Borrower_Email;");
    dc.Append("}");

    CompilerResults compiledResult = CompileScript(dc.ToString());

    if (compiledResult.Errors.HasErrors)
    {
        Console.WriteLine (compiledResult.Errors[0].ErrorText);
        throw new InvalidOperationException("Invalid Expression syntax");
    }

    Assembly assembly = compiledResult.CompiledAssembly;

    // This is just for testing purposes.
    FieldInfo field = assembly.GetType("FileRow").GetField("Borrower_First_Name");          
    Console.WriteLine (field.Name);         
    Console.WriteLine (field.FieldType);
}

public static CompilerResults CompileScript(string source) 
{ 
    CompilerParameters parms = new CompilerParameters(); 

    parms.GenerateExecutable = false; 
    parms.GenerateInMemory = true; 
    parms.IncludeDebugInformation = false; 

    CodeDomProvider compiler = CSharpCodeProvider.CreateProvider("CSharp"); 

    return compiler.CompileAssemblyFromSource(parms, source); 
} 
Vlad Bezden
  • 59,971
  • 18
  • 206
  • 157
  • Thanks, I will try shortly. I need to do something like this to use it: FileRow fileRow = ParseFileHelperDataRow(rowID, lineContents, sqlConn); So once I do what you show above, then I would use reflection to create my object? – NealWalters Feb 23 '12 at 15:45
  • After you do what I showed you will have now object in your assembly. If you look at my example bellow it shows how you can call properties on that object, but you can also assign properties/fields, call methods on it. For instance if you have method you could get that method assembly.GetType("FileRow").GetMethod("MyMethod); and then call invoke on it. assembly variable that you get is fully compiled your code and you can do with it anything like regular objects/compiled code. – Vlad Bezden Feb 23 '12 at 17:01
  • So I need to populate my object with values, before I try to extract those values; I'll try something like this: Object myObject = assembly.GetType("FileRow"); myObject = ParseFileHelperDataRow(rowID, lineContents, sqlConn); – NealWalters Feb 23 '12 at 20:25