Adding support for dynamic file loaders in c#

2019-09-07 07:47发布

问题:

If I have a Console App in C# that reads in files of a certain format and converts them to business objects, I design this by having an IReader interface so that I can support different formats, eg XML, CSV, pipe delimited etc, and have different concrete classes for each file format.

If the requirement is to be able to load new file readers (new formats) in dynamically without having to recompile, is there a way I can accomplish that?

The only way I can think of is somehow using XSD or reg expressions but it seems to me there should be a better solution

回答1:

This sounds like you want a plugin mechanism for loading your IReaders dynamically. There are plenty of examples out there.

Simple plugin mechanism sample

SO discussion



回答2:

You could use reflection. Each implementation of IReader could go in a distinct DLL. You would also create an Attribute to tag each implementation of IReader that states which file format it handles.

public sealed class InputFormatAttribute : Attribute
{
    private string _format;
    public string Format
    {
        get { return format; }
    }
    public InputFormatAttribute(string format)
    {
        _format = format;
    }
}

[InputFormat("CSV")]
public class CSVReader : IReader
{
    // your CSV parsing code here
    public BusinessObject Parse(string file)
    {}
}



BusinessObject LoadFile(string fileName)
{
    BusinessObject result = null;
    DirectoryInfo dirInfo = new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
    FileInfo[] pluginList = dirInfo.GetFiles("*.DLL");

    foreach (FileInfo plugin in pluginList)
    {
        System.Reflection.Assembly assem = System.Reflection.Assembly.LoadFile(fileInfo.FullName);
        Type[] types = assem.GetTypes();
        Type type = types.First(t => t.BaseType == "IReader");

        object[] custAttrib = type.GetCustomAttributes(typeof(InputFormatAttribute), false);
        InputFormatAttribute at = (InputFormatAttribute)custAttrib[0];

        if (at.Format.Equals(Path.GetExtension(fileName).Substring(1), StringComparison.CurrentCultureIgnoreCase))
        {
            IReader reader = (IReader)assem.CreateInstance(type.FullName);
            return reader.Parse(fileName);
        }

    }

    // got here because not matching plugin found
    return null;

}


回答3:

Depends on the complicity of Readers, you may decide to use CodeDom to let someone to write the code directly. Short example:

            // create compiler
            CodeDomProvider provider = CSharpCodeProvider.CreateProvider("C#");
            CompilerParameters options = new CompilerParameters();
            // add more references if needed
            options.ReferencedAssemblies.Add("system.dll");
            options.GenerateExecutable = false;
            options.GenerateInMemory = true;
            // compile the code
            string source = "using System;namespace Bla {public class Blabla { public static bool Test { return false; }}}";
            CompilerResults result = provider.CompileAssemblyFromSource(options, source);
            if (!result.Errors.HasErrors)
            {
                Assembly assembly = result.CompiledAssembly;
                // instance can be saved and then reused whenever you need to run the code
                var instance = assembly.CreateInstance("Bla.Blabla");
                // running some method
                MethodInfo method = instance.GetType().GetMethod("Test"));
                var result = (bool)method.Invoke(_formulas, new object[] {});
            }

But probably you'll have to provide sort of editor or accomplish the task partially (so only necessary code have to be written).



标签: c# filereader