snussel
snussel

Reputation: 75

Is there a way to convert a string into a type?

I am learning generics in C#. So this may be simple for experienced folks.

I have 71 different models and I want to be able to generically store data from CSV into them.

The processing part is not super hard, I have this method signature:

private static async Task ProcessFileAsync<T>(string currentFile) where T : class, new()

The hard part is calling it. I have one CSV file for each model that I want to place data into. The Name of the CSV file is identical to the Model's name (ex: Product.csv would correspond to the Product model).

Ideally, I would like to just send the name in the caller, but I am getting a "X is a variable but is used like a type" Compiler error.

I could have a massive switch statement to solve this issue, but that seems relatively wasteful.

Any assistance would be appreciated.

Put another way, I could do the following:

        switch(justFName)
        {
            case "Address":
                _ = ProcessFileAsync<Address>(ci.FullName);
                break;
            case "Currency":
                _ = ProcessFileAsync<Currency>(ci.FullName);
                break;
                ...
                ...
                ...And so on
                ...
                ...
            default:
                //No method for this file name
                break;
        }

instead I would like to have something like this: _ = ProcessFileAsync<justFName>(ci.FullName);

Upvotes: 4

Views: 112

Answers (2)

Marlonchosky
Marlonchosky

Reputation: 526

The @GuruStron's answer is very good. Another way of achieving what you need is only using Reflection. However, like @GuruStrong suggest, it's good for you that include an annotation in the classes where the search will be performed, or put them in a single assembly. The following code works only if these classes are in the same assembly.

#region These classes must be in the same assembly
public class Address {
}

public class Currency {
} 
#endregion

class Program {
    static async Task Main(string[] args) {
        var justFName = "Currency";
        var fullName = "name";
        var type = typeof(Address).Assembly.GetTypes()
            .Single(x => x.Name == justFName);
        var method = typeof(Program).GetMethod(nameof(ProcessFileAsync),
                BindingFlags.NonPublic | BindingFlags.Static)
            .MakeGenericMethod(type);
        await (Task)method.Invoke(null, new object[] { fullName });

        Console.ReadLine();
    }

    private static async Task ProcessFileAsync<T>(string currentFile) where T : class, new() {
        Console.WriteLine(currentFile);
    }
}

Upvotes: 0

Guru Stron
Guru Stron

Reputation: 143373

If you can somehow determine all classes you need to handle from your assembly (personally I like to mark them with specially created attribute), then Reflection and Expression Trees to the rescue:

public class Address { }

public class MethodHolder // Just dummy class to hold your process action
{
    public static async Task ProcessFileAsync<T>(string currentFile) where T : class, new()
    {
        Console.WriteLine(currentFile);
    }
}

public static class Processor
{
    private static readonly Dictionary<string, Action<string>> _dict;
    static Processor()
    {
        var types = typeof(Address).Assembly.GetTypes()
            // filter your types correctly here somehow
            // JIC do not forget to verify that they satisfy
            // your generic constraints
            .Where(t => t.Name == "Address");
        _dict = types.ToDictionary(t => t.Name, BuildAction);
    }

    private static Action<string> BuildAction(Type t)
    {
        var method = typeof(MethodHolder).GetMethod(nameof(MethodHolder.ProcessFileAsync))
        .MakeGenericMethod(t);

        var param = Expression.Parameter(typeof(string));
        return Expression.Lambda<Action<string>>(
                Expression.Call(method, param), 
                param)
            .Compile();
    }

    // TODO: add some nice handling for keys not in dictionary
    public static void Process(string key, string value) => _dict[key](value);
}

And usage: Processor.Process(nameof(Address), "testfilename"); (nameof just for the sake of example)

Upvotes: 3

Related Questions