模式匹配
首先我想介绍在 switch 语句中使用模式匹配来处理不同的类型,如下所示:
public static double ComputeAreaModernSwitch(object shape)
{
switch (shape)
{
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Rectangle r:
return r.Height * r.Length;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
示例取自 Pattern Matching - C# Guide。
输入字典
除此之外,是的,你可以写一本字典……问题在于项目的类型。
我们可以这样做:
Dictionary<Type, Action<object>> dictionary;
// (initialize and populate somewhere else) ...
if (dictionary.TryGetValue(element.GetType(), out var action))
{
action(element);
}
但是,在这里你必须使用Action<object>,因为我们需要为项目指定类型(不,我们不能说Action<?> - 好吧,我们可以使用Action<dynamic>,但你不能转换@987654329 @ to Action<dynamic>),强制你在被调用的方法中进行强制转换。
我们可以争辩说,强制转换是一种告诉编译器我们知道一些它不知道的东西的方法。在这种情况下,我们知道 那个 对象实际上是给定类型的。
我们可以做得更好/更差,这取决于你如何看待它......
Dictionary<Type, Delegate> dictionary;
// (initialize and populate somewhere else) ...
if (dictionary.TryGetValue(element.GetType(), out var @delegate))
{
@delegate.DynamicInvoke(element);
}
这实际上是后期绑定。我们在编译时不知道类型......作为开发人员,您必须确保提供正确类型的委托。但是,如果我们已经强制执行编译器不知道的知识,那么这是可以接受的。
我们可以制作一个辅助方法来使它更容易:
void SetMethod<T>(Action<T> action)
{
dictionary[typeof(T)] = action;
}
这里编译器可以检查方法的类型是否正确。然而,从编译器的角度来看,当您使用字典时,此信息会丢失(不可用)。如果你愿意,这是一种类型擦除。
动态
现在,如果我们放弃类型,我们可以在 answer 之后使用 dynamic TheGeneral。
附录:调用已知方法(使用 MethodInfo)
您可以通过名称调用方法,例如,如果您有以下内容:
class Helper
{
public static void Method(T input)
{
Console.WriteLine(input.GetType());
}
}
你可以这样做:
var methodInfo = typeof(Helper).GetMethod("Method");
// ...
methodInfo.Invoke(null, new object[]{element});
然后您可以将所有方法放入一个帮助类中,并通过名称(您可以从类型的名称派生)找到它们。
如果要调用具有泛型参数的已知方法,可以使用 MethodInfo。我们需要知道方法是否是静态的,以及泛型参数是否是方法定义或声明类型定义的一部分...
一方面,如果你有这样的事情:
class Helper<T>
{
public static void Method(T input)
{
Console.WriteLine(input.GetType());
}
}
你可以这样做:
var helperType = typeof(Helper<>);
// ...
var specificMethodInfo = helperType.MakeGenericType(element.GetType()).GetMethod("Method");
specificMethodInfo.Invoke(null, new object[]{element});
另一方面,如果你有这个:
class Helper
{
public static void Method<T>(T input)
{
Console.WriteLine(input.GetType());
}
}
你可以这样做:
var methodInfo = typeof(Helper).GetMethod("Method");
// ...
var specificMethodInfo = methodInfo.MakeGenericMethod(element.GetType());
specificMethodInfo.Invoke(null, new object[]{element});
注意:我将null 作为第一个参数传递给调用。这就是我调用该方法的实例。没有,因为它们是静态的。如果不是,那么您需要一个实例...例如,您可以尝试使用 Activator.CreateInstance 创建一个。
附录:查找要调用的内容(类型发现)
也许你有不同的方法要调用(它们不一样,但有不同的泛型参数),但你不想有手动填充字典的麻烦。
这就是类型发现的用武之地。
首先,我建议使用一个属性,例如:
[AttributeUsage(AttributeTargets.Method)]
public sealed class DataHandlerAttribute : Attribute { }
然后我们需要一个我们将要搜索的类型的列表。如果我们要搜索一个已知的程序集,我们可以这样做:
var assembly = typeof(KnownType).GetTypeInfo().Assembly;
var types = assembly.GetTypes();
注意:如果您的目标平台不支持此功能(.NET Standard 1.0 到 1.4),您将不得不手动编写类型列表。
接下来,我们需要一个谓词来检查给定类型是否是我们感兴趣的类型之一:
bool IsDataHandlerMethod(MethodInfo methodInfo)
{
var dataHandlerAttributes = return (DataHandlerAttribute[])item.GetCustomAttributes(typeof(DataHandlerAttribute), true);
if (attributes == null || attributes.Length == 0)
{
return false;
}
if (methodInfo.DeclaringType != null)
{
return false;
}
if (methodInfo.ReturnTpye != typeof(void))
{
return false;
}
var parameters = methodInfo.GetParameters();
if (parameters.Length != 1)
{
return false;
}
if (paramters[0].IsByRef || paramters[0].IsOut)
{
return false;
}
return true;
}
以及将它们转换为委托的方法:
(Type, Delegate) GetTypeDelegatePair(MethodInfo methodInfo)
{
var parameters = methodInfo.GetParameters();
var parameterType = parameters[0].ParameterType;
var parameterTypeArray = new []{parameterType};
var delegateType = typeof(Action<>).MakeGenericType(parameterTypeArray);
var target = null;
if (!methodInfo.IsStatic)
{
var declaringType = methodInfo.DeclaringType;
target = instance = Activator.CreateInstance(declaringType);
}
return (parameterType, methodInfo.CreateDelegate(delegateType, target));
}
现在我们可以这样做了:
var dataHandlers = types
.SelectMany(t => t.GetTypeInfo().GetMethods())
.Where(IsDataHandlerMethod)
.Select(GetTypeDelegatePair);
我们将有一对可枚举的类型和委托,我们可以使用它们来填充我们的字典。
注意:上面的代码仍然需要做一些工作(例如,我们可以只调用一次GetParameters 吗?),并假定一个现代.NET 目标(需要额外的工作才能使其工作)在旧平台上)。另请注意,我提供的类型发现代码不处理泛型方法,您可以检查 Type.IsGenericTypeDefinition 和 MethodInfo.IsGenericMethodDefinition... 但是,我建议避免使用它们。实际上,对于要将所有方法放在单个静态类中的情况,应该很容易修改。例如,您也可以使用类似的方法来获取工厂方法。