无法将 null 赋值给匿名数组类型的属性

41
我有一个包含(Pilot)对象的数组,这些对象具有一个可能为空的(Hanger)属性,该属性本身具有一个(List<Plane>)属性。出于测试目的,我想将其简化和“铺平”到一个匿名对象中,该对象具有属性 PilotName(字符串)和 Planes(数组),但不确定如何处理空的 Hanger 属性或空的 PlanesList
(为什么使用匿名对象?因为我测试的 API 对象是只读的,我希望测试是“声明性”的:自包含、简单和易读...但我也愿意听取其他建议。此外,我正在尝试了解更多关于 LINQ 的知识。)
例子
class Pilot
{
    public string Name;
    public Hanger Hanger;
}

class Hanger
{
    public string Name;
    public List<Plane> PlaneList;
}

class Plane
{
    public string Name;
}

[TestFixture]
class General
{
    [Test]
    public void Test()
    {
        var pilots = new Pilot[]
        {
            new Pilot() { Name = "Higgins" },
            new Pilot()
            {
                Name = "Jones", Hanger = new Hanger()
                {
                    Name = "Area 51",
                    PlaneList = new List<Plane>()
                    {
                        new Plane { Name = "B-52" },
                        new Plane { Name = "F-14" }
                    }
                }
            }
        };

        var actual = pilots.Select(p => new
        {
            PilotName = p.Name,
            Planes = (p.Hanger == null || p.Hanger.PlaneList.Count == 0) ? null : p.Hanger.PlaneList.Select(h => ne
            {
                PlaneName = h.Name
            }).ToArray()
        }).ToArray();

        var expected = new[] {
            new { PilotName = "Higgins", Planes = null },
            new
            {
                PilotName = "Jones",
                Planes = new[] {
                    new { PlaneName = "B-52" },
                    new { PlaneName = "F-14" }
                }
            }
        };

        Assert.That(actual, Is.EqualTo(expected));
    }

直接的问题是这条语句 expected... Planes = null 出现错误,错误原因是:

无法将 null 赋值给匿名类型属性,但承认潜在问题可能是在第一次使用 nullactual 中就不是最佳方法。

有没有想法可以将 expected 中的空数组赋值或采取与 actual 不同的方法而不使用 null


1
你更新的示例主要问题在于你正在检查一个枚举器(由pilots.Where调用创建的WhereSelectArrayIterator)和一个数组之间的相等性。你可以在Where子句的末尾使用ToArray()将其强制转换为数组。 - Andras Zoltan
对于更广泛的问题,我想说两件事:首先,一个非常好的改变是考虑使(例如)Planes成为一个空集合而不是**null**,当没有任何项时。这样处理起来会更容易;这里有一些背景阅读材料。其次,我怀疑你将不得不使实际和期望之间的相等检查更加“智能”;也就是说,它将不得不深入对象和集合,并使用类似SequenceEqual的东西。 - AakashM
@AndrasZoltan:你是对的,问题现在已经被纠正 :) - petemoloy
@AakashM:空集合:我已经做了背景阅读并同意,但我无法在我的代码中得到所需的更改。你能提供一个修复建议吗?谢谢。附言:你对Assert也是正确的 :) - petemoloy
@AakashM:我已经将我的尝试附加到问题的末尾,但出现了编译错误。你能提供一个修复建议吗?再次感谢。 - petemoloy
显示剩余2条评论
3个回答

83

你必须使用一个类型化null

(List<Plane>)null

或者

(Plane[])null
否则编译器无法确定您希望匿名类型成员的类型是什么。

更新 正如 @AakashM 正确指出的那样-这解决了您将 null 赋值给匿名成员的问题-但实际上它不能编译-如果它编译了,也不会允许您引用这些成员。

修复方法是这样做(不幸的是,null 和匿名 Planes 数组都需要转换:

var expected = new[] {
  new { 
          PilotName = "Higgins", 
          Planes = (IEnumerable)null
      },
  new {
          PilotName = "Higgins", 
          Planes = (IEnumerable)new [] {
                              new { PlaneName = "B-52" },
                              new { PlaneName = "F-14" } 
                          }
      }
};

所以使用 IEnumerable 作为成员类型。您还可以使用 IEnumerable<object>,但无论哪种方法效果都相同。

或者 - 您可以使用 IEnumerable<dynamic> 作为通用类型 - 这将使您能够执行以下操作:

Assert.AreEqual("B-52", expected[1].Planes.First().PlaneName);

但是现在,因为 OP 想要类型是另一个匿名类型的数组,所以没有办法引用它。 - AakashM
啊,这是个很好的观点。也许IEnumerable可以解决这个问题,但它会丧失编译时的类型信息。 - Andras Zoltan
是的,切换到 IEnumerable 可以解决问题 - 但我们可以在不改变被测试代码的情况下修复测试代码;请参见我的答案。 - AakashM
好主意!谢谢Andras。 - Anytoe
2
为了完整起见:null as Plane[] 也可以工作。 - Robin Hartmann

42

有两个问题需要解决:

首先,当你使用new { Name = Value }构造一个匿名类型的实例时,编译器需要能够确定Value类型,只有单独的null没有类型,所以编译器无法确定给你的Planes成员什么类型。

现在,如果你使用了一个命名类型作为值,你可以简单地写成(type)null,但是因为你想要另一个匿名类型的数组,没有办法引用它(因为它是匿名的!)。

那么,如何将null转换为匿名类型的数组呢?好吧,C#规范保证拥有相同名称和类型(以相同顺序!)成员的匿名类型是统一的;也就是说,如果我们这样写:

var a = new { Foo = "Bar" };
var b = new { Foo = "Baz" };

那么 ab 具有相同的类型。我们可以利用这个事实来获取我们适当类型的 null,方法如下:

var array = (new[] { new { PlaneName = "" } });
array = null;

虽然不太美观,但它却能正常工作——现在array的类型正确,但是它的值为null。 因此,这段代码可以编译:

var array = new[] {
  new {
    PlaneName = ""
  }
};
array = null;

var expected = new[] {
  new {
    PilotName = "Higgins",
    Planes = array
  },
  new {
    PilotName = "Higgins",
    Planes = new[] {
      new {
        PlaneName = "B-52"
      },
      new {
        PlaneName = "F-14"
      }
    }
  }
};

这真是太棒了(虽然有点奇怪!)- 我之前读过这个,但从未找到过用途。 - Andras Zoltan
4
一种替代方法是将 new[] { new { PlaneName = "" } }.Take(0).ToArray() 内联。 - Enigmativity

14

使用default(Plane[])替代null


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接