【问题标题】:Cannot assign null to anonymous property of type array无法将 null 分配给数组类型的匿名属性
【发布时间】:2013-01-14 10:57:05
【问题描述】:

我有任何具有 (Hanger) 属性的 (Pilot) 对象数组,它可能为 null,它本身具有 (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 行错误,

不能分配给匿名类型属性,但承认潜在的问题可能是在actual 中使用null 是使用null 并不是最好的方法。

任何想法如何在expected 中分配空数组或采用与actual 中的null 不同的方法?

【问题讨论】:

  • 更新示例的主要问题是因为您正在检查枚举数(由pilots.Where 调用创建的WhereSelectArrayIterator)和数组之间的相等性。您可以在Where 子句末尾使用ToArray() 将其强制放入数组中。
  • 对于更广泛的问题,我想说两件事:首先,要考虑的一个很好的改变是让(例如)Planes 成为一个 空集合 而不是 null,当没有任何项目时。处理起来容易得多; here's some background reading。其次,我怀疑您将不得不使实际和预期之间的相等性检查更加“智能”;即,它必须深入研究对象和集合,并使用SequenceEqual 之类的东西。
  • @AndrasZoltan:你是对的,问题现在更正了:)
  • @AakashM:空集合:我做了背景阅读,我同意,但我无法在我的代码中获得所需的更改。你能建议一个修复吗?谢谢。 p.s.你对断言也是正确的:)
  • 嗯,当然有时-null Hanger 更难......也许是Null Object Pattern。对于集合,只需返回 Enumerable.Empty&lt;type&gt; 而不是 null

标签: c# linq anonymous-types


【解决方案1】:

你必须使用 typed 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&lt;object&gt;,但是效果都是一样的。

或者 - 你可以使用 IEnumerable&lt;dynamic&gt; 作为通用类型 - 这可以让你这样做:

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

【讨论】:

  • ...但是现在,由于OP希望该类型是另一个匿名类型的数组,因此无法引用它。
  • 啊,这很好。 IEnumerable 可能会这样做,但它确实会清除任何编译时类型知识。
  • 是的,切换到IEnumerable 会起作用——但我们可以在不更改被测试代码的情况下修复测试代码;看我的回答。
  • 好主意!谢谢安德拉斯
  • 为了完整起见:null as Plane[] 也可以。
【解决方案2】:

发生了两件事:

首先,当您使用new { Name = Value} 构造匿名类型的实例时,为了构建该类型,编译器需要能够计算出Value类型。只是 null 本身没有类型,所以编译器不知道给你的 Planes 成员提供什么类型。

现在,如果您使用命名类型作为值,您可以说 (type)null 并完成,但是因为您想要另一个匿名类型的数组,所以没有办法引用 to 是(它是匿名的!)。

那么如何将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"
      }
    }
  }
};

【讨论】:

  • 这很甜蜜(如果也有点奇怪的话!) - 我以前读过它,但从未找到它的用途。
  • 另一种方法是内联new[] { new { PlaneName = "" } }.Take(0).ToArray()
【解决方案3】:

只需使用default(Plane[]) 而不是null

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-07
    • 2014-06-04
    • 2019-07-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多