Given:
List<string> argsLet'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?
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) {
...
}