提问人:Nick Farsi 提问时间:8/22/2022 更新时间:8/22/2022 访问量:66
防止通用方法中的多播
Prevent multicasting in generic method
问:
我正在制作用于检查输入的通用验证器:
接口:
public interface IInputValidator
{
bool CanHandle<T>();
bool Validate<T>(string? input, out T result);
}
实现:
public class IntegerValidator : IInputValidator
{
public bool CanHandle<T>()
{
return typeof(T) == typeof(int);
}
public bool Validate<T>(string? input, out T result)
{
var isValid = int.TryParse(input, out var res);
result = (T)(object)res;
return isValid;
}
}
然后我抓住我拥有的所有验证器并像这样注入:(接口本身不是通用的,这感觉很方便,因此我不必一个接一个地注入它们,并且能够将它们分组到一个集合中)
private readonly IEnumerable<IInputValidator> _inputValidators;
public CallerClass(IEnumerable<IInputValidator> inputValidators)
{
_inputValidators = inputValidators;
}
并这样称呼它:
var validator = _inputValidators.First(r => r.CanHandle<int>());
var isInputValid = validator.Validate(userInput, out int id);
除了实现中的这一行外,一切看起来都很好
result = (T)(object)res;
我觉得这里有些不对劲,但不知道如何让它变得更好。不过,它的工作原理是这样的。
答:
核心问题是,您正在尝试将相应验证器的解析和该验证器的操作组合到同一个通用接口中。
如果您愿意将解析器和验证器功能分成两个接口:
public interface IInputValidatorResolver
{
bool CanHandle<T>();
IInputValidator<T> GetValidator<T>();
}
public interface IInputValidator<T>
{
bool Validate(string? input, out T result);
}
您可以使用 CallerClass 合约中的实例来解析您调用的相应验证程序和强类型,而无需对象强制转换。解析器实现可以创建一个缓存,将 Validator 强制转换为通用实例。IInputValidatorResolver
IInputValidator<T>
public class IntegerValidatorResolver : IInputValidatorResolver
{
public bool CanHandle<T>() => typeof(T) == typeof(int);
public IInputValidator<T> GetValidator<T>() => Cache<T>.Validator;
private static class Cache<T>
{
public static readonly IInputValidator<T> Validator = BuildValidator();
private static IInputValidator<T> BuildValidator() => ((IInputValidator<T>)new IntegerValidator());
}
}
public class LongValidatorResolver : IInputValidatorResolver
{
public bool CanHandle<T>() => typeof(T) == typeof(long);
public IInputValidator<T> GetValidator<T>() => Cache<T>.Validator;
private static class Cache<T>
{
public static readonly IInputValidator<T> Validator = BuildValidator();
private static IInputValidator<T> BuildValidator() => ((IInputValidator<T>)new LongValidator());
}
}
public class IntegerValidator : IInputValidator<int>
{
public bool Validate(string? input, out int result) => int.TryParse(input, out result);
}
public class LongValidator : IInputValidator<long>
{
public bool Validate(string? input, out long result) => long.TryParse(input, out result);
}
您可以使用以下方法对其进行测试:
IEnumerable<IInputValidatorResolver> validatorResolvers = new List<IInputValidatorResolver> { new IntegerValidatorResolver(), new LongValidatorResolver() };
var intValidator = validatorResolvers.First(x => x.CanHandle<int>()).GetValidator<int>();
var isIntValid = intValidator.Validate(long.MaxValue.ToString(), out int intResult);
Console.WriteLine(isIntValid);
Console.WriteLine(intResult);
var longValidator = validatorResolvers.First(x => x.CanHandle<long>()).GetValidator<long>();
var isLongValid = longValidator.Validate(long.MaxValue.ToString(), out long longResult);
Console.WriteLine(isLongValid);
Console.WriteLine(longResult);
也就是说,这会产生一个尴尬的合约,如果你不首先执行检查,你的调用可能会引发异常。此外,在此实现或当前实现中,您必须遍历解析器/验证器以找到适当的实例,这是不必要的浪费。CanHandle<T>
GetValidator<T>
因此,拥有一个知道如何根据 T 类型解析适当验证器的实例可能更有意义,而无需检查。IInputValidatorResolver
CanHandle<T>()
public interface IInputValidatorResolver
{
IInputValidator<T> GetValidator<T>();
}
public class ValidatorResolver : IInputValidatorResolver
{
public IInputValidator<T> GetValidator<T>() => Cache<T>.Validator;
private static class Cache<T>
{
public static readonly IInputValidator<T> Validator = BuildValidator();
private static IInputValidator<T> BuildValidator()
{
if (typeof(T) == typeof(int))
{
return ((IInputValidator<T>)new IntegerValidator());
}
else if (typeof(T) == typeof(long))
{
return ((IInputValidator<T>)new LongValidator());
}
else
{
throw new ArgumentException($"{typeof(T).FullName} does not have a registered validator.");
}
}
}
}
public interface IInputValidator<T>
{
bool Validate(string? input, out T result);
}
public class IntegerValidator : IInputValidator<int>
{
public bool Validate(string? input, out int result) => int.TryParse(input, out result);
}
public class LongValidator : IInputValidator<long>
{
public bool Validate(string? input, out long result) => long.TryParse(input, out result);
}
这允许更干净的 API,并且注册和枚举要少得多:
IInputValidatorResolver resolver = new ValidatorResolver();
var intValidator = resolver.GetValidator<int>();
var isIntValid = intValidator.Validate(long.MaxValue.ToString(), out int intResult);
Console.WriteLine(isIntValid);
Console.WriteLine(intResult);
var longValidator = resolver.GetValidator<long>();
var isLongValid = longValidator.Validate(long.MaxValue.ToString(), out long longResult);
Console.WriteLine(isLongValid);
Console.WriteLine(longResult);
更新看起来你想要一个纯粹的构造函数注入驱动的解决方案。为此,可以将新接口注册为并将其注入解析程序实例。IEnumerable<IInputValidator>
该接口负责在强制转换为 之前检查的方法。IInputValidator
CanHandle<T>()
IInputValidator<T>
public interface IInputValidatorResolver
{
IInputValidator<T> GetValidator<T>();
}
public class ValidatorResolver : IInputValidatorResolver
{
private IEnumerable<IInputValidator?> _validators;
public ValidatorResolver(IEnumerable<IInputValidator?> validators)
{
_validators = validators;
}
public IInputValidator<T> GetValidator<T>()
{
foreach (var validator in _validators)
{
if (validator!.CanHandle<T>())
{
return (IInputValidator<T>)validator;
}
}
throw new ArgumentException($"{typeof(T).FullName} does not have a registered validator.");
}
}
public interface IInputValidator
{
bool CanHandle<TInput>();
}
public interface IInputValidator<T> : IInputValidator
{
bool Validate(string? input, out T result);
}
public class IntegerValidator : IInputValidator<int>
{
public bool CanHandle<T>() => typeof(T) == typeof(int);
public bool Validate(string? input, out int result) => int.TryParse(input, out result);
}
public class LongValidator : IInputValidator<long>
{
public bool CanHandle<T>() => typeof(T) == typeof(long);
public bool Validate(string? input, out long result) => long.TryParse(input, out result);
}
您可以使用以下命令测试此行为:
var servicesCollection = new ServiceCollection();
servicesCollection.AddTransient(typeof(IEnumerable<IInputValidator>), s =>
{
return new List<IInputValidator>
{
new IntegerValidator(),
new LongValidator()
};
});
servicesCollection.AddTransient<IInputValidatorResolver, ValidatorResolver>();
var serviceProvider = servicesCollection.BuildServiceProvider();
var resolver = serviceProvider.GetService<IInputValidatorResolver>();
var intValidator = resolver.GetValidator<int>();
var isIntValid = intValidator.Validate(long.MaxValue.ToString(), out int intResult);
Console.WriteLine(isIntValid);
Console.WriteLine(intResult);
var longValidator = resolver.GetValidator<long>();
var isLongValid = longValidator.Validate(long.MaxValue.ToString(), out long longResult);
Console.WriteLine(isLongValid);
Console.WriteLine(longResult);
使用此方法,不再需要 ValidationResolver。它只是封装 的逻辑的一种方式。你可以很容易地注入到你的消费类中,并在每次需要使用它时执行强制转换。GetValidator<T>
IEnumerable<IInputValidator>
评论
另一种选择是使用 Autofac 的 IComponentContext:
using Autofac;
using TransactionStorage.Interface;
namespace TransactionStorage.Core
{
public class InputResolver : IInputResolver
{
private readonly IComponentContext _context;
public InputResolver (IComponentContext context)
{
_context = context;
}
public bool Validate<T>(string? userInput, out T result) where T : struct
{
var validator = _context.Resolve<IInputValidator<T>>();
return validator.Validate(userInput, out result);
}
}
}
评论
(T)(object)res
.Validate(userInput, out int id)
int.TryParse(input, out var res);
int