簡體   English   中英

C# 通用接口和工廠模式

[英]C# Generic Interface and Factory Pattern

我正在嘗試創建一個通用接口,其中一種方法的參數類型由泛型定義

編輯

在意識到我可能通過在工廠創建方法中指定類型參數來混淆問題之后,我稍微改變了這個問題。 我有兩種類型的 API 調用,我需要向第 3 方 API 進行調用。 第一個使用 int 的 Id 從 API 檢索記錄。 第二個還從 API 檢索記錄,但 Id 是一個字符串 (guid)。 對於每種記錄類型(ClientEntity 和 InvoiceEntity),我都有一個 class,它們都實現了一個通用接口,我在其中傳入了 Id 類型

這是我在其中聲明帶有 id 參數的方法的接口

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

我在幾個類中實現了接口,一個將 id 設置為 int,另一個設置為 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
    }
}

我想知道的是如何在工廠模式中使用它?

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

}

目標是使用工廠實例化正確的 class 以便我可以調用 ProcessEntity 方法

編輯

我不想將通用類型傳遞給工廠方法,因為工廠創建的 class 應該處理它。 當我創建 object 時,我不知道需要什么 Id 類型,我希望工廠處理

例如

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

或者

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

我希望這是有道理的

通常對於那個工廠使用一些DI容器(例如,當GenericInt或GenericString具有依賴關系時DI可能很有用),但是為了演示如何解決這個問題:

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

你應該可以做這樣的事情:

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

如果你想使用靜態類,但標記正確的答案很好但是如果你想返回DI注入類型而不是新建一個對象怎么辦? 我建議如下!

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

請注意,為了清晰起見,我只是在構造函數中注入了兩個預期的返回類型。 我可以將工廠實現為Dictionary並將返回對象注入此Dictionary。 希望能幫助到你。

public class Program
{
    public static void Main(string[] args)
    {
        List<Number> list = new();
        Do(list);
    }
    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 ChildOfFactory();
          throw new Exception("");
     }
     public static T Create(int max)
     {
          return (Factory<T>)ConcreteF()).Build();
     }
     protected abstract T Build();
}

我想你不想輸入類似於 LINQ 方法的類型參數。 然而,這背后的魔力發生了,因為類型參數用於普通參數定義。 例如,在ToList<string>()方法中,您可以看到在括號之間使用了TSource

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

如果從IEnumerable<string>調用時調用ToList()而不是ToList<string>() ,編譯器就是這樣知道你想要List<string> string>

但是,我認為您的工廠方法中根本不需要泛型類型參數。 您所要做的就是創建TGeneric<TId>的非通用版本

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

並從CreateGeneric方法中刪除<WhatGoesHere>

public static IGeneric CreateGeneric(string recordType)
{
    if (recordType == "Client")
    {
        return new ClientEntity();
    }
    if (recordType == "Invoice")
    {
        return new InvoiceEntity();
    }
    return null;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM