C# Generic Interface and Factory Pattern
Asked Answered
D

6

32

I am trying to create a Generic interface where the parameter type of one of the methods is defined by the generic

EDIT

I've changed the question slightly after realising I have probably confused matters by specifying a type parameter in the Factory creation method. What I have is two types of API calls that I need to make to a 3rd party API. The first retrieves a record from the API using an Id that is an int. The second also retrieves a record from the API but the Id is a string (guid). I have a class for each record type (ClientEntity and InvoiceEntity) that both implement a Generic Interface where I pass in the Id type

This is the Interface in which I declare a Method with an id Parameter

public interface IGeneric<TId>
{
    void ProcessEntity(TId id);
}

I implement the interface in a couple of classes, one sets the id to be an int, the other a string.

public class ClientEntity: IGeneric<int> // Record with Id that is an int
{
    public void ProcessEntity(int id)
    {
        Console.WriteLine(id);
        // call 3rd party API with int Id
    }
}

public class InvoiceEntity: IGeneric<string> // Record with Id that is a string (guid)
{
    public void ProcessEntity(string id)
    {
        Console.WriteLine(id);
        // call 3rd party API with string Id
    }
}

What I would like to know is how do I use this within a factory pattern?

public static class GenericFactory
{
    public static IGeneric<WhatGoesHere> CreateGeneric(string recordType)
    {
        if (recordType == "Client")
        {
            return new ClientEntity();
        }
        if (type == "Invoice")
        {
            return new InvoiceEntity();
        }

        return null;
    }

}

The objective is to use the factory to instantiate the correct class so that I can call the ProcessEntity method

EDIT

I don't want to have to pass in the Generic type to the factory method because the class that is created by the factory should handle that. When I create the object, I don't know what Id type is required, I want the factory to handle that

e.g.

   var myGeneric = GenericFactory.CreateGeneric("Client");
   myGeneric.ProcessEntity("guid")

or

   var myGeneric = GenericFactory.CreateGeneric("Invoice");
   myGeneric.ProcessEntity(1234)

I hope that makes sense

Druggist answered 8/9, 2016 at 8:55 Comment(4)
It seems confusing that your interface has a property Id and a method that takes Id as a parameter. Are you sure that's what you want to be doing? If so, you should make it clear what the difference between the two values is.Agribusiness
You can probably ignore the property, it was just intended to show the use of TDruggist
The return type from your proposed factory would have to be object, which is unlikely to be much use to you... unless you use dynamic, but I'd advise against that...Anvers
Thanks @Mathew but as you suggest, a return type of object sort of negates the advantage of the factory pattern because I would need to cast it to something to call the methodsDruggist
A
38

You should be able to do something like this:

public static class GenericFactory
{
    public static IGeneric<T> CreateGeneric<T>()
    {
        if (typeof(T) == typeof(string))
        {
            return (IGeneric<T>) new GenericString();
        }

        if (typeof(T) == typeof(int))
        {
            return (IGeneric<T>) new GenericInt();
        }

        throw new InvalidOperationException();
    }
}

You would use it like this:

var a = GenericFactory.CreateGeneric<string>();
var b = GenericFactory.CreateGeneric<int>();

Note that this uses a strongly-typed call rather than passing in the type name as a string (which may or may not be what you actually want).


If instead you want to pass a string for the type name, you will have to return an object because there is no way to return the actual type:

public static object CreateGeneric(string type)
{
    switch (type)
    {
        case "string": return new GenericString();
        case "int":    return new GenericInt();
        default:       throw new InvalidOperationException("Invalid type specified.");
    }
}

Obviously if you have an object you would normally have to cast it to the right type in order to use it (which requires that you know the actual type).

Alternatively, you could use reflection to determine what methods it contains, and call them that way. But then you'd still need to know the type in order to pass a parameter of the right type.

I think that what you are attempting to do here is not the right approach, which you will discover once you start trying to use it.

Hacky solution: Use dynamic

Nevertheless, there is one way you can get something close to what you want: Use dynamic as follows (assuming that you are using the object CreateGeneric(string type) factory method from above):

dynamic a = GenericFactory.CreateGeneric("string");
dynamic b = GenericFactory.CreateGeneric("int");

a.ProcessEntity("A string");
b.ProcessEntity(12345);

Be aware that dynamic uses reflection and code generation behind the scenes, which can make the initial calls relatively slow.

Also be aware that if you pass the wrong type to a method accessed via dynamic, you'll get a nasty runtime exception:

dynamic a = GenericFactory.CreateGeneric("string");
a.ProcessEntity(12345); // Wrong parameter type!

If you run that code, you get this kind of runtime exception:

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: The best overloaded method match for 'ConsoleApplication1.GenericString.ProcessEntity(string)' has some invalid arguments
   at CallSite.Target(Closure , CallSite , Object , Int32 )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
   at ConsoleApplication1.Program.Main() in D:\Test\CS6\ConsoleApplication1\Program.cs:line 71
Anvers answered 8/9, 2016 at 9:1 Comment(8)
But sometimes when invoking GenericFactory.CreateGeneric, only a string type name is given, instead of strongly-typed T.Filamentary
@DannyChen I think that's just the way the OP thought he would have to do it. Will need the OP to confirm.Anvers
@DannyChen You can't be provided with a strong-typed object if you do not know the type. Therefore, if you only have the type-name as a string value, then you cannot handle both a IGeneric<string> and/or IGeneric<int> when you do not know in advance which you'll get.Merry
@Merry I knew that. I was indicating a situation that OP might have.Filamentary
Thanks, I like this idea but the only problem I have is that when I call the factory CreateGeneric method I am passing in a different type of parameter (not the type). I have updated the question to make it clearer because I realise it was probably confusing. Thanks for all your helpDruggist
@NickSmith You simply can't do what you want without knowing the type - the only possible return type that you could use is object, and then you'd have to cast it to use it at some point, or use reflection to access its properties and methods.Anvers
@NickSmith I've extended my answer.Anvers
Thanks @MatthewWatson. It might just be simpler to not use generics in that case and set the interface to accept an object in the method's id parameter and then cast during the implementation of the methodDruggist
G
23

Usually for that Factory using some DI container (DI can be useful, for example, when GenericInt or GenericString has dependencies), but to demonstrate just Idea how you can resolve this:

void Main()
{
    GenericFactory.CreateGeneric<int>();
    GenericFactory.CreateGeneric<string>();
}

public static class GenericFactory
{
    private static Dictionary<Type, Type> registeredTypes = new Dictionary<System.Type, System.Type>();

    static GenericFactory()
    {
        registeredTypes.Add(typeof(int), typeof(GenericInt));
        registeredTypes.Add(typeof(string), typeof(GenericString));
    }

    public static IGeneric<T> CreateGeneric<T>()
    {
        var t = typeof(T);
        if (registeredTypes.ContainsKey(t) == false) throw new NotSupportedException();

        var typeToCreate = registeredTypes[t];
        return Activator.CreateInstance(typeToCreate, true) as IGeneric<T>;
    }

}

public interface IGeneric<TId>
{
    TId Id { get; set; }

    void ProcessEntity(TId id);
}

public class GenericInt : IGeneric<int>
{
    public int Id { get; set; }

    public void ProcessEntity(int id)
    {
        Console.WriteLine(id);
    }
}

public class GenericString : IGeneric<string>
{
    public string Id { get; set; }

    public void ProcessEntity(string id)
    {
        Console.WriteLine(id);
    }
}
Grefer answered 8/9, 2016 at 9:5 Comment(0)
V
3

The answer marked correct is fine if you want to use Static class but but what if you want to return an DI injected type instead of newing an object? I suggest the following!

public interface IGenericFactory
{
    IGeneric<T> GetGeneric<T>() where T : class;
}

public class GenericFactory: IGenericFactory
{
    private readonly IGeneric<int> intGeneric;
    private readonly IGeneric<string> stringGeneric;
    public GenericFactory(IGeneric<int> intG, IGeneric<string> stringG)
    {
        intGeneric = intG;
        stringG = stringG;
    }

    public IGeneric<T> GetGeneric<T>() where T : class
    {
        if (typeof(T) == typeof(IGeneric<int>))
            return (IGeneric<T>)Convert.ChangeType(intGeneric, typeof(IGeneric<T>));

        if (typeof(T) == typeof(IGeneric<string>))
            return (IGeneric<T>)Convert.ChangeType(stringGeneric,typeof(IGeneric<T>));
        else 
            throw new NotSupportedException();
    }
}

Please note i simply injected the two expected return types for clarity in the constructor. I could have implemented the factory as a Dictionary and injected the return objects into this Dictionary. Hope it helps.

Vaginectomy answered 20/2, 2019 at 16:22 Comment(0)
A
0

If the function does not know the type, make it generic.

If the children are generics of different types (<int>, <string>), return object and cast inside the same factory class (Factory<T>), It is safe by typeof.

Personally, I prefer to specify the type with generics, without using an additional parameter, eg a string.

public class Program
{
    public static void Main(string[] args)
    {
        List<Number> something = new();
        Do(something);
    }
    public static void Do<T>(List<T> list) 
    {
        list.Add(Factory<T>.Create());
    }
}

public abstract class Factory<T>
{
     private static Object ConcreteF()
     {
          if (typeof(T) == typeof(Number))
                return new ChildGenericNumber();
          throw new Exception("");
     }
     public static T Create()
     {
          return (Factory<T>)ConcreteF()).Build();
     }
     protected abstract T Build();
}
Aluminium answered 13/9, 2022 at 13:41 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Hundredfold
V
0

I'm thinking you don't want to have to enter the type parameter similar to the LINQ methods. However the magic behind that happens because the type parameter is used in the normal parameter definitions. For example in the ToList<string>() method you can see that TSource is used between the parenthesis.

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source);

That's how the compiler knows that you want a List<string> if you call ToList() instead of ToList<string>() when called from an IEnumerable<string>

However, I don't think you need a generic type parameter in your factory method at all. All you have to do is create a non-generic version of your TGeneric<TId>

public interface IGeneric { }
public interface IGeneric<TId> : IGeneric
{
    void ProcessEntity(TId id);
}

And remove the <WhatGoesHere> from the CreateGeneric method:

public static IGeneric CreateGeneric(string recordType)
{
    if (recordType == "Client")
    {
        return new ClientEntity();
    }
    if (recordType == "Invoice")
    {
        return new InvoiceEntity();
    }
    return null;
}
Victor answered 17/9, 2022 at 20:23 Comment(0)
O
0

What is required could be achieved may be not by Factory but a kind of Processor type of implementation.

Let say below is the generic interface:

public interface IGeneric<TId>
{
    void ProcessEntity(TId id);
}

Let say 2 classes which implement it are:

public class GenericInt : IGeneric<int>
{
        // DI Ctor
        public GenericInt(IDep1 dep, IDep2 dep2)
        {
            
        }
        public void ProcessEntity(int id)
        {
        }
}

public class GenericString : IGeneric<string>
{
        // DI Ctor
        public GenericString(IDep1 dep, IDep2 dep2)
        {
            
        }
        public void ProcessEntity(string id)
        {
        }
}

Now the below class is not exactly a factory but kind of processor:

public class Processor
{
    public Processor(IServiceProvider)
    {
       _serviceProvider = serviceProvider;
    }
    public void Execute(object o)
    {
        var handlerType = typeof(IGeneric<>).MakeGenericType(o.GetType());
        var handler = _serviceProvider.GetService(handlerType);
        var method = handlerType.GetMethod("ProcessEntity");
        method.Invoke(handler, new object[] { o });
    }
} 

It can be used as below:

   public class Program
    {
       public void Main()
       {
         // Here ioc is actually service provider -> IServiceProvider
         var handlerProvider = ioc.GetRequiredService<Processor>();
         handlerProvider.Execute("string"); // string
         handlerProvider.Execute(123); // int
       }
      
    }

Above approach won't give the object of the type but in a generic way we can execute the required function without the orchestrating code (Program in our case) knowing about it.

Ossieossietzky answered 10/9, 2023 at 12:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.