C# 通用接口和工厂模式
C# Generic Interface and Factory Pattern
我正在尝试创建一个通用接口,其中方法之一的参数类型由通用接口定义
编辑
在意识到我可能通过在 Factory 创建方法中指定类型参数混淆了问题后,我稍微更改了问题。我有两种类型的 API 调用,我需要向第三方 API 进行调用。第一个使用作为 int 的 Id 从 API 检索记录。第二个也从 API 中检索记录,但 Id 是一个字符串 (guid)。对于每种记录类型(ClientEntity 和 InvoiceEntity),我都有一个 class,它们都实现了一个通用接口,我在其中传入了 Id 类型
这是我在其中声明带有 id 参数的方法的接口
public interface IGeneric<TId>
{
void ProcessEntity(TId id);
}
我在几个 classes 中实现了接口,一个将 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;
}
}
objective就是使用工厂实例化正确的class这样我就可以调用ProcessEntity方法
编辑
我不想将泛型类型传递给工厂方法,因为工厂创建的 class 应该可以处理这个问题。当我创建对象时,我不知道需要什么 Id 类型,我希望工厂处理那个
例如
var myGeneric = GenericFactory.CreateGeneric("Client");
myGeneric.ProcessEntity("guid")
或
var myGeneric = GenericFactory.CreateGeneric("Invoice");
myGeneric.ProcessEntity(1234)
我希望这是有道理的
你应该可以这样做:
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>();
请注意,这使用强类型调用而不是将类型名称作为字符串传递(这可能是也可能不是您真正想要的)。
如果您想为类型名称传递字符串,则必须 return 和 object
因为无法 return 实际类型:
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 容器的 Factory(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);
}
}
如果您想使用静态 class,则标记为正确的答案很好 class 但是如果您
想要 return 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();
}
}
请注意,为了清楚起见,我只是在构造函数中注入了两个预期的 return 类型。我本可以将工厂实现为字典并将 return 对象注入到该字典中。希望能帮助到你。
我正在尝试创建一个通用接口,其中方法之一的参数类型由通用接口定义
编辑
在意识到我可能通过在 Factory 创建方法中指定类型参数混淆了问题后,我稍微更改了问题。我有两种类型的 API 调用,我需要向第三方 API 进行调用。第一个使用作为 int 的 Id 从 API 检索记录。第二个也从 API 中检索记录,但 Id 是一个字符串 (guid)。对于每种记录类型(ClientEntity 和 InvoiceEntity),我都有一个 class,它们都实现了一个通用接口,我在其中传入了 Id 类型
这是我在其中声明带有 id 参数的方法的接口
public interface IGeneric<TId>
{
void ProcessEntity(TId id);
}
我在几个 classes 中实现了接口,一个将 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;
}
}
objective就是使用工厂实例化正确的class这样我就可以调用ProcessEntity方法
编辑
我不想将泛型类型传递给工厂方法,因为工厂创建的 class 应该可以处理这个问题。当我创建对象时,我不知道需要什么 Id 类型,我希望工厂处理那个
例如
var myGeneric = GenericFactory.CreateGeneric("Client");
myGeneric.ProcessEntity("guid")
或
var myGeneric = GenericFactory.CreateGeneric("Invoice");
myGeneric.ProcessEntity(1234)
我希望这是有道理的
你应该可以这样做:
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>();
请注意,这使用强类型调用而不是将类型名称作为字符串传递(这可能是也可能不是您真正想要的)。
如果您想为类型名称传递字符串,则必须 return 和 object
因为无法 return 实际类型:
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 容器的 Factory(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);
}
}
如果您想使用静态 class,则标记为正确的答案很好 class 但是如果您 想要 return 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();
}
}
请注意,为了清楚起见,我只是在构造函数中注入了两个预期的 return 类型。我本可以将工厂实现为字典并将 return 对象注入到该字典中。希望能帮助到你。