c#通用接口和工厂模式

本文关键字:工厂 模式 接口 | 更新日期: 2023-09-27 18:06:14

我试图创建一个泛型接口,其中一个方法的参数类型是由泛型

定义的

编辑

在意识到我可能通过在Factory创建方法中指定类型参数而混淆了问题后,我稍微改变了问题。我需要对第三方API进行两种类型的API调用。第一个方法使用一个int型的Id从API检索一条记录。第二个方法也从API检索一条记录,但是Id是一个字符串(guid)。我为每个记录类型(ClientEntity和InvoiceEntity)都有一个类,它们都实现了一个泛型接口,其中我传入Id类型

这是我声明带有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方法

编辑

我不想把泛型类型传递给工厂方法,因为工厂创建的类应该处理它。当我创建对象时,我不知道需要什么Id类型,我想让工厂来处理

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

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

我希望你能理解

c#通用接口和工厂模式

您应该能够这样做:

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容器的工厂(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);
    }
}

标记正确的答案是好的,如果你想使用静态类,但如果你想要返回一个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。希望能有所帮助。

我想你不想输入类似于LINQ方法的类型参数。然而,这背后的神奇之处在于,类型参数是在普通参数定义中使用的。例如,在ToList<string>()方法中,您可以看到TSource被用在括号之间。

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

当从IEnumerable<string>调用ToList()而不是ToList<string>()时,编译器知道你想要一个List<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;
}

如果函数不知道类型,则将其设置为泛型。

如果子类是不同类型的泛型(),返回对象并在同一个工厂类中强制转换(Factory),则通过typeof.

是安全的。

就我个人而言,我更喜欢用泛型指定类型,而不使用额外的参数,例如字符串。

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