A List of Field Names as a String Array - LINQ Exp

2019-09-02 14:20发布

问题:

Hello MVC and LINQ Experts,

I have a Model that looks like this:

 public class SomeClass : IValidatableObject
 {
    public string SomeString { get; set; }
    public string SomeString2 { get; set; }
    public int SomeInteger { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
       //... IF there is some error...THEN
       yield return new ValidationResult("Some Error Message.", GetFieldNames(() => new []{ this.SomeString }));
    }

 }

As you can see, I am calling GetFieldNames that takes an expression, and returns to you the expression members as a string array. According to a book I read recently, the way to link an error to a field is to pass it as a string as follows:

  yield return new ValidationResult("Some Error Message.", new []{ "SomeString" }));

But I wanted to be Strongly Typed, so here is the method that I wrote:

  public static string[] GetFieldNames(Expression<Func<object[]>> exp)
    {
        //Build a string that will in the end look like this: field1,field2,field3
        //Then we split(',') it into an array and return that string array. 
        string fieldnames = "";

        MemberExpression body = exp.Body as MemberExpression;

        if (body == null)
        {   
            NewArrayExpression ubody = (NewArrayExpression)exp.Body;
            foreach(MemberExpression exp2 in ubody.Expressions)
            {
                fieldnames += exp2.Member.Name + ",";
            }
            fieldnames = fieldnames.TrimEnd(',');

        }
        if(fieldnames.Length > 0)
            return fieldnames.Split(',');
        else
            return new string[]{};
    }

Current Usage:

GetFieldNames(() => new[] { this.SomeString , this.SomeString2 });

Output:

{ "SomeString" , "SomeString2" }

This works fine.

The problem is that if I use it as follows, it gives me an error (compile time):

GetFieldNames(() => new[] { this.SomeString , this.SomeInteger });

Error: No best type found for implicitly-typed array

My Desired Output:

{ "SomeString" , "SomeInteger" }

I can't pass in an array of object because int is not a complex type.

How can I pass the function an expression array with int and string?

回答1:

You could try passing an array of objects (which is what your expression expects) instead of trying to use an array initializer syntax:

GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger });

This allows you to pass arbitrary object types.



回答2:

You could define an interface IFieldName that enables usage in your list, and then implement it in different classes (int, error, string, etc.) for the actual types that occur in your processing.

This is roughly equivalent to defining an rray of object, but restores type-safety.



回答3:

With the help of Darin Dimitri (the idea to pass a new object[] instead of new [] The following code will make sure that your IValidatableObject can now be strongly typed instead of just an array of strings.

    public static string[] GetFieldNames(Expression<Func<object[]>> exp)
    {
        string fieldnames = "";

        MemberExpression body = exp.Body as MemberExpression;

        if (body == null)
        {
            NewArrayExpression ubody = (NewArrayExpression)exp.Body;
            foreach (Expression exp2 in ubody.Expressions)
            {
                if (exp2 is MemberExpression) {
                    fieldnames += ((MemberExpression)exp2).Member.Name + ",";
                }
                else {
                    var op = ((UnaryExpression)exp2).Operand;
                    fieldnames += ((MemberExpression)op).Member.Name + ",";
                } 
            }
            fieldnames = fieldnames.TrimEnd(',');

        }

        if(fieldnames.Length > 0)
            return fieldnames.Split(',');
        else
            return new string[]{};
    }

Usage:

GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger }));

Usage for MVC Validation:

yield return new ValidationResult("Some Error.", GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger }));