Reputation: 75
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
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
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