Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Generic Interface and Factory Pattern

Tags:

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

like image 534
Nick Smith Avatar asked Sep 08 '16 08:09

Nick Smith


1 Answers

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 
like image 59
Matthew Watson Avatar answered Nov 11 '22 17:11

Matthew Watson