2016-09-08 2 views
4

Я пытаюсь создать универсальный интерфейс, где тип параметра одного из методов определяется родовымC# Общий интерфейс и Factory Pattern

EDIT

Я изменил вопрос немного после того, как понимая, что я, вероятно, путал вопросы, указав параметр типа в методе создания фабрики. У меня есть два типа API-вызовов, которые мне нужно сделать для стороннего API. Первый извлекает запись из API с использованием идентификатора, который является int. Второй также извлекает запись из API, но Id - строка (guid). У меня есть класс для каждого типа записи (ClientEntity и InvoiceEntity), что и реализовать общий интерфейс, где я прохожу в типе Id

Это интерфейс, в котором я объявляю метод с идентификатором параметра

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

Я реализую интерфейс в нескольких классах, один устанавливает id как int, а другой - в строку.

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 
    } 
} 

Что я хотел бы знать, как это использовать в рамках заводского шаблона?

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; 
    } 

} 

Цель состоит в том, чтобы использовать завод, чтобы создать экземпляр правильный класс, так что я могу вызвать метод ProcessEntity

EDIT

Я не хочу, чтобы пройти в Generic введите фабричный метод, потому что класс, созданный фабрикой, должен это обработать. Когда я создаю объект, я не знаю, какой тип идентификатора требуется, я хочу, чтобы фабрика обрабатывала это значение

например.

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

или

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

Я надеюсь, что имеет смысл

+0

кажется запутанным, что ваш интерфейс имеет свойство 'Id' _AND_ метод, который принимает' Id' в качестве параметра. Вы уверены, что это то, что вы хотите делать? Если это так, вы должны четко указать, какая разница между этими двумя значениями. – Rik

+0

Вы, вероятно, может игнорировать свойство, он просто хотел показать использование T –

+0

типа возвращаемого из предложенного вами завода должен быть 'object', который вряд ли будет много пользы для вас ... если вы не используете' dynamic, но я бы посоветовал это сделать ... –

ответ

4

Вы должны быть в состоянии сделать что-то вроде этого:

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(); 
    } 
} 

Вы бы использовать его как это:

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

Обратите внимание, что это использует строго типизированный вызов, а не передачу имени типа в виде строки (которая может или не может быть тем, что вы действительно хотите).


Если вместо этого вы хотите передать строку для имени типа, вам придется вернуть object, потому что нет никакого способа, чтобы вернуть фактический тип:

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."); 
    } 
} 

Очевидно, что если у вас есть object, вы, как правило, должны использовать его для правильного типа, чтобы использовать его (что требует, чтобы вы знали фактический тип).

В качестве альтернативы, вы можете использовать отражение, чтобы определить, какие методы он содержит, и называть их таким образом. Но тогда вам все равно нужно знать тип, чтобы передать параметр нужного типа.

Я думаю, что вы пытаетесь сделать здесь не правильный подход, который вы обнаружите, как только вы начинаете пытаться использовать его.

Hacky решение: Используйте dynamic

Тем не менее, есть один способ, вы можете получить что-то близкое к тому, что вы хотите: Используйте dynamic следующим образом (при условии, что вы используете метод object CreateGeneric(string type) заводского сверху):

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

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

Помните, что dynamic использует отражение и генерации кода за кулисами, что позволяет сделать начальные звонки относительно медленными.

Также следует знать, что если вы передаете неправильный тип к способу доступа через dynamic, вы получите неприятное исключение во время выполнения:

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

Если запустить этот код, вы получите этот вид исключения во время выполнения :

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 
+0

Но иногда при вызове 'GenericFactory.CreateGeneric' указывается только имя типа' string', а не строго типизированное 'T'. –

+0

@ DannyChen Я думаю, что именно так OP думал, что он должен будет это сделать. Для подтверждения потребуется OP. –

+0

@ DannyChen Вам не может быть предоставлен объект с сильным типом, если вы не знаете тип. Поэтому, если у вас есть только имя-типа в качестве значения строки, то вы не можете обрабатывать одновременно '' IGeneric и/или '' IGeneric , когда вы не знаете заранее, который вы получите. – Maarten

5

Обычно для этого завода, используя некоторый DI контейнер (DI может быть полезным, например, когда GenericInt или GenericString имеет зависимость), но и продемонстрировать только идею, как можно решить эту проблему:

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); 
    } 
}