Validate a List<string> args dynamically in C#

Viewed 147

Given:

  • Number of arguements
  • Type of each arguement
  • List<string> args

Let's say I have a List<string> args as follows:

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

I want to validate args as follows , I can call any one of these methods as required by my code.

Validation Methods:

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

Problem: As you can see, I will eventually end up having infinite number of validation methods. Is there a way so that I can have only 1 validation method like for example below? (which can take care of all possible cases)

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

And then I can just call the above method isValid(args, 3, List<string>{"float", "Int32", "Double"}) ? But I am getting error in my generic method, anyone know how to validate datatypes generically and dynamically?

3 Answers

How about:

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]);

So instead of isValidOneFloatTwoDoubleThreeInt32(args) you'd use IsValid<float, double, int>(args);

etc, for some small upper bound N of TArgN. The problem there is: you then need a generics-based parser, but that isn't necessarily too bad if you only need to be able to handle specific types, by checking the 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);
}

Maybe it would help if you had such a method for arbitrary validators:

public static bool IsValid<T>(List<T> args, params Func<List<T>, bool>[] validators)
    => validators.All(validate => validate(args));

Now you could for example validate it in this way:

bool isValid = IsValid(args, list => list.Count == 3);

or with an existing method:

bool isValid = IsValid(args, isValidThreeStrings);

or with both:

bool isValid = IsValid(args, list => list.Count == 3, isValidThreeStrings);

or with multiple:

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

If you like it and want to reuse it for all kinds of lists/arrays/whatever you could create an extension method like this. Note that it takes 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));
}

I suggest extracting model, let it be a dictionary with Type as a key, and validator Func as a value:

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

Then validator can be:

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

Usage:

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

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

If you want to have strings instead of Types change s_Validators and IsValid a bit:

  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) {
    ...
  }
Related