简体   繁体   中英

List.Any() returning true when false was expected

We ran into a bug in our code today. We have a couple of lists where the key of that data is an enum. There are multiple different enums that are used as keys (Foo.Bar1 and Foo.Bar2 in the code below).

All tests have a list of DataFields containg 1 item where the key is set to one of the enum values. The first and last test run as expected. The second test was expected to succeed, but fails. When reading the code it seems legit.

My assumption is that by unboxing the variables, the enum values are converted to there integer values and those are compared. Which make them equal, thus returning true, thus making the Any() method returning true as well. Is this correct? Or is there anything else going on?

We should have written the comparison as in the third test, with an equals() method...

If recreated a very simplified version of the issue in the unit test below.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;

namespace EnumCastTest
{
    [TestClass]
    public class UnitTest1
    {
        public class DataField
        {
            public Enum Key { get; set; }
        }

        class Foo
        {
            public enum Bar1 { A }
            public enum Bar2 { B }
        }


        [TestMethod]
        public void Field_With_Bar1_A_Should_Return_True()
        {
            List<DataField> fields = new List<DataField> {
                new DataField() { Key = Foo.Bar1.A} };

            Assert.IsTrue(fields.Any(q => (Foo.Bar1)q.Key == Foo.Bar1.A));
        }

        [TestMethod]
        public void Field_Without_Bar1_A_Should_Return_False()
        {
            List<DataField> fields = new List<DataField> {
                new DataField() { Key = Foo.Bar2.B} };

            Assert.IsFalse(fields.Any(q => (Foo.Bar1)q.Key == Foo.Bar1.A));
        }

        [TestMethod]
        public void Field_Without_Bar1_A_Should_Return_False2()
        {
            List<DataField> fields = new List<DataField> {
                new DataField() { Key = Foo.Bar2.B} };

            Assert.IsFalse(fields.Any(q => Foo.Bar1.A.Equals(q.Key)));
        }
    }
}

This happens because the following returns true:

var x = Foo.Bar1.A;
var y = (Foo.Bar2)x;
Console.WriteLine(y == Foo.Bar2.B);

Enums are internally represented by a integral value, by default the type being used is an int :

Every enumeration type has an underlying type, which can be any integral type except char. The default underlying type of enumeration elements is int.

This underlying type is used when casting one enum type into another. So for the enum value, the integral value is being used, and then based on that, the new enum value is being created.

Essentially, this process works like this:

var x = Foo.Bar1.A;
int integral = (int)x;
var y = (Foo.Bar2)integral;

Unless you specify the number explicitly with each enum member, by default the position in the enum declaration decides the integral value, starting with 0 .

So in above example, integral would be 0 , since Bar1.A is the first member of Bar1 . And the first member of Bar2 is Bar2.B , so that's what you get as the result.

So the reason the test fails is because when casting one enum into another type, the type identity is lost. In general, enums only make a real sense in their own domain, ie when you compare one member with another member of the same enum.

I suppose, that every enum is converted to its underlying type - Int32. and as you don't set values for each of them they assumed as zero.

you're setting the Foo.Bar2 enum to the value B and you're checking the Foo.Bar1 enum for the value A. try checking the correct enum. like in the below code:

new DataField() { Key = Foo.Bar1.B} };

        Assert.IsFalse(fields.Any(q => (Foo.Bar1)q.Key == Foo.Bar1.A));

I guess that you want both the enum value and the type to be the same for the Any(...) to return true.

    [TestMethod]
    public void Field_Without_Bar1_A_Should_Return_False()
    {
        List<DataField> fields = new List<DataField> {
            new DataField() { Key = Foo.Bar2.B} };

        Assert.IsFalse(fields.Any(q => (q.Key is Bar1) && (Foo.Bar1)q.Key == Foo.Bar1.A));
    }

If the first predicate is true, the second one is evaluated.

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