使用C#动态验证List <string> args

3

给定:

  • 参数数量
  • 每个参数的类型
  • List<string> args

假设我有一个如下所示的 List<string> args

List<string> args = new List<string> { "1", "helloworld", "3" }

我想要对args进行以下验证,根据我的代码需要调用其中任何一个方法。

验证方法:

public bool isValidOneString(List<string> args)
{
      return args.Count() == 1;
}
public bool isValidTwoStrings(List<string> args)
{
      return args.Count() == 2;
}
public bool isValidThreeStrings(List<string> args)
{
      return args.Count() == 3;
}
public bool isValidOneStringTwoFloat(List<string> args)
{
      bool isValid = args.Count() == 2;
      if(!isValid) return false;

      float valueAfterParse;
      isValid = float.TryParse(args[1], out valueAfterParse);
      return isValid;
}
public bool isValidOneFloatTwoDoubleThreeInt32(List<string> args)
{
      bool isValid = args.Count() == 3;
      if(!isValid) return false;

      float valueAfterParse;
      isValid = float.TryParse(args[0], out valueAfterParse);
      if(!isValid) return false;

      Double valueAfterParse;
      isValid = Double.TryParse(args[0], out valueAfterParse);
      if(!isValid) return false;

      Int32 valueAfterParse;
      isValid = Int32.TryParse(args[0], out valueAfterParse);
      if(!isValid) return false;

      return isValid;
}

问题: 正如您所看到的,我最终将拥有无限数量的验证方法。 是否有一种方法可以只使用一个验证方法,就像下面的例子一样?(它可以处理所有可能的情况)

public bool isValid(List<string> args, int totalExpectedCountOfArgs, List<string> typesOfEachArg)
{
      bool isValid = args.Count() == totalExpectedCountOfArgs;
      if(!isValid) return false;
      
      int i = 0;
      foreach(string dataType : typesOfEachArg) {
           isValid = typeOf(dataType).TryParse(args[i], out typeOf(dataType)); //I AM GETTING ERROR HERE BECAUSE I DONT KNOW HOW TO GENERIFY THIS
           if(!isValid) return false;
           i++;
      }
      return true;
}

那么我只需要调用上述方法 isValid(args, 3, List<string>{"float", "Int32", "Double"}) 吗?但是我在我的泛型方法中遇到了错误,有人知道如何以通用且动态的方式验证数据类型吗?


1
有多少种类型?仅限于int/float/string吗?还是包括任何有效的.NET类型? - mikelegg
仅限整数/浮点数/字符串,标准数据类型,没有特殊的,有限数量。 - user1735921
3个回答

2
如何呢:
public bool IsValid<TArg0>(List<string> args)
    => args.Length == 1
    && IsValidCore<TArg0>(args[0]);

public bool IsValid<TArg0, TArg1>(List<string> args);
    => args.Length == 2
    && IsValidCore<TArg0>(args[0])
    && IsValidCore<TArg1>(args[1]);

public bool IsValid<TArg0, TArg1, TArg2>(List<string> args)
    => args.Length == 3
    && IsValidCore<TArg0>(args[0])
    && IsValidCore<TArg1>(args[1])
    && IsValidCore<TArg2>(args[2]);

所以,不再使用isValidOneFloatTwoDoubleThreeInt32(args),而是使用IsValid<float, double, int>(args);

等等,对于TArgN的一些小的上限N。问题在于:你需要一个基于泛型的解析器,但如果只需要处理特定类型,通过检查T,这并不一定太糟糕:

private static bool IsValidCore<T>(string value)
{
    if (typeof(T) == typeof(string)) return true;
    if (typeof(T) == typeof(int)) return int.TryParse(...);
    if (typeof(T) == typeof(float)) return float.TryParse(...);
    if (typeof(T) == typeof(double)) return double.TryParse(...);
    // etc for some finite number of types
    throw new NotSupportedException("Not considered: " + typeof(T).Name);
}

很好的方法,我正在尝试这个。 - user1735921

2
也许对于任意验证器,拥有这样的方法会有所帮助:
public static bool IsValid<T>(List<T> args, params Func<List<T>, bool>[] validators)
    => validators.All(validate => validate(args));

现在你可以用以下方式验证它:
bool isValid = IsValid(args, list => list.Count == 3);

或使用现有的方法:

bool isValid = IsValid(args, isValidThreeStrings);

或同时使用两者:
bool isValid = IsValid(args, list => list.Count == 3, isValidThreeStrings);

或者有多个:

Func<List<string>, bool>[] allValidators = new Func<List<string>, bool>[]
{ 
    list => list.Count == 2, 
    list => float.TryParse(list[1], out float val), 
    isValidOneFloatTwoDoubleThreeInt32
};
bool isValid = IsValid(args, allValidators);

如果您喜欢它并希望将其重用于所有类型的列表/数组/任何类型,您可以创建一个像这样的扩展方法。请注意,它需要IEnumerable<T>

public static class EnumerableExtensions
{
    public static bool AreAllValid<T>(this IEnumerable<T> items, params Func<IEnumerable<T>, bool>[] validators)
        => validators?.All(validate => validate(items)) ?? throw new ArgumentNullException(nameof(validators));
}

很好,但为什么不使用扩展方法呢? - spzvtbg
@spzvtbg:我一开始并不想过于复杂化它,我不知道OP是否了解扩展方法。此外,如果我开始编写扩展方法,我不会扩展List<T>而是IList<T>(甚至是IEnumerable<T>),这会让OP更难理解为什么需要这些更改。但是,是的,你是对的,它可能是扩展的一个好候选。 - Tim Schmelter
我并没有考虑到那一点,我只是想知道是否有什么反对扩展方法的意见。而且我完全支持各种数据结构的最抽象使用。 - spzvtbg
@spzvtbg:将其添加到末尾,也许能帮助某人。 - Tim Schmelter

1
我建议提取模型,让它成为一个以Type为键,验证器Func为值的字典
 private static Dictionary<Type, Func<string, bool>> s_Validators = 
   new Dictionary<Type, Func<string, bool>>() {
     {typeof(string), (x) => true },
     {typeof(int), (x) => int.TryParse(x, out var _) },
     {typeof(float), (x) => float.TryParse(x, out var _) },
     {typeof(double), (x) => double.TryParse(x, out var _) },  
      //TODO: add more type validators here  
 };

那么验证器可以是:

private static bool IsValid(IEnumerable<string> arguments, params Type[] signature) {
  if (null == arguments)
    return false; // or throw ArgumentNullException
  if (null == signature)
    return false;

  int index = 0;

  foreach (string arg in arguments) {
    // Too many arguments 
    if (index >= signature.Length)
      return false;  

    // For argument to be valid we should know type and pass validation
    if (!s_Validators.TryGetValue(signature[index++], out var validator) || 
        !validator(arg))
      return false;
  }    

  // if index < signature.Length we have too few arguments
  return index == signature.Length;
}

使用方法:

  List<string> args = new List<string> { "1", "helloworld", "3" };

  bool isValid = IsValid(args, typeof(int), typeof(string), typeof(int));

如果你想使用字符串而不是类型,请稍微更改 s_ValidatorsIsValid
  private static Dictionary<string, Func<string, bool>> s_Validators = 
    new Dictionary<string, Func<string, bool>>() {
      {"string", (x) => true },
      {"int", (x) => int.TryParse(x, out var _) },
       ...
  };      

  private static bool IsValid(IEnumerable<string> arguments, params string[] signature) {
    ...
  }

好的,谢谢你提供这个,我也会试一下。 - user1735921

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接