简体   繁体   中英

Enum failing == when value retrieved via reflection

I am trying to implement a custom jQuery Unobtrusive Validation attribute for MVC 6. Below is the IsValid() implementation for an attribute that looks at an adjacent property in a class and compares it to a compile-time constant.

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    ValidationResult result = ValidationResult.Success;

    // Simplest case, there is something in this field
    if (!string.IsNullOrEmpty(value?.ToString()))
        return result;

    // Check relative field
    try
    {
        // Grab the property referenced via reflection
        var relativeProperty = validationContext.ObjectType.GetProperty(this._relativePropertyName);

        // Compare the runtime value of that property to the Attribute initialized value
        if (relativeProperty.GetValue(validationContext.ObjectInstance) == this._relativeValue)
        {
            // Fail if those 2 values are equal
            result = new ValidationResult(this.ErrorMessageString); 
        }

    }
    catch (Exception)
    {
        result = new ValidationResult(this.ErrorMessageString);
    }

    return result;
}

For the most part, this works exactly as expected. The only issue I am having is when the property referenced and its value are a derivative of an Enum (for example, let's pretend our enum is Result ). Implementing this attribute would look like the following in a class:

public class TestObject
{
    public Result TestResult { get; set; }
    [IsEqual(nameof(TestResult), Result.Pass)]
    public string PassComment { get; set; }
}

When the debugger gets to the line if(relativeProperty.GetValue(validationContext.ObjectInstance) == this._relativeValue) it fails, even if relativeProperty.GetValue(validationContext.ObjectInstance) resolves as Result.Pass . Using the Immediate Window, relativeProperty.GetValue(validationContext.ObjectInstance) = Result.Pass and also this._relativeValue = Result.Pass

Small side note, calling .GetType() on both values are also equal.

I'm assuming this has something to do with boxing, but can't pinpoint it. Thanks in advance.

Don't use == to evaluate equality for things that aren't strongly typed. Consider the following:

void Main()
{
    Console.WriteLine(((object)Result.Pass) == (object)Result.Pass);
    // False

    Console.WriteLine(((object)Result.Pass).Equals((object)Result.Pass));
    // True

    Console.WriteLine(object.Equals((object)Result.Pass,(object)Result.Pass));
    // True
}

public enum Result{
    Pass, Fail
}

In C#, the == operator can be overridden to make syntax easier when comparing certain types of objects, which has the advantage of providing compile-time errors if you try to == two incompatible types. And some people think it looks nicer.

However, when values are down-cast to object s, the object == object operator performs an Object.ReferenceEquals() check. Since enums are Value Types, they have to get "boxed" into a new object in order to be cast as objects , and those new objects are going to exist in different memory locations. Hence, ReferenceEquals() will be false.

Instead, use the object.Equals() method, which passes to the individual class's .Equals() override. In this case, the Enum type's override of .Equals() will check whether the value can be cast to a Result , and whether the resulting value is the same as this one's.

The advantage to object.Equals(value1, value2) over value1.Equals(value2) is that you don't get a null exception if value1 is null.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM