Cannot assign null to anonymous property of type array
我有一个(Pilot 对象数组,它有一个(Hanger 属性,可以是空的,它本身有一个(List 属性。为了测试的目的,我想将其简化并"扁平化"为一个具有属性PilotName 和Planes 的匿名对象,但不确定如何处理空的Hanger 属性或空的PlanesList 。
(为什么是匿名对象?因为我测试的API对象是只读的,我希望测试是"声明性的":自包含的、简单的和可读的…但我愿意接受其他建议。另外,我还想了解更多关于Linq的信息。)
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
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 的错误是,
Cannot assign to anonymous type property but admit the underlying problem may be that using null in actual is using null is not the best approach in the first place.
对于如何在expected 中分配空数组,或者采用与actual 中的null 不同的方法,有什么想法吗?
更新示例的主要问题是,您检查的是枚举器(由pilots.Where 调用创建的WhereSelectArrayIterator 与数组之间的相等性。您可以在Where 子句的末尾使用ToArray() 将其强制为数组。
对于更广泛的问题,我想说两件事:首先,要考虑的一个很好的改变是,当没有任何项目时,使(例如)Planes 成为一个空的集合,而不是null 。处理起来容易得多,这里有一些背景资料。第二,我怀疑你必须在实际的和预期的之间做一个更加"智能"的平等检查;也就是说,它必须深入到对象和集合中,并使用类似于SequenceEqual 的东西。
@安德拉斯佐尔坦:你是对的,问题现在纠正了:)
@aakashm:empty collection:我做了背景阅读,我同意,但我不能在我的代码中得到所需的更改。你能提出一个解决办法吗?谢谢。另外,你对断言也正确:)
嗯,当然,有时使用null Hanger ,更难……可能是空的对象模式。对于托收,只返回Enumerable.Empty ,而不是null 。
@aakashm:我已经将我的尝试附加到了问题的结尾,但是有一个编译错误。你建议修理一下?再次感谢。
@彼得莫洛伊说,数组中的两个对象实际上是不同的类型,因为虽然Enumerable.Empty() 和List 都实现了IEnumerable ,但它们不是完全相同的类型。把.AsEnumerable() 放在new List<>() 后面,编译
@阿卡什:明白了。再次感谢你的帮助。
必须使用键入的空值:
或
否则,编译器不知道您希望匿名类型的成员是什么类型。
更新正如@aakashm正确指出的那样-这解决了将null 分配给匿名成员的问题-但实际上并没有编译-如果这样做,就不允许您引用这些成员。
解决方法是这样做(不幸的是,null 和匿名Planes 数组都需要强制转换:
1 2 3 4 5 6 7 8 9 10 11 12 13
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 ,但效果都是一样的。
或者-您可以使用IEnumerable 作为通用类型-这样可以做到:
1
Assert. AreEqual ( "B-52" , expected[ 1 ] . Planes . First ( ) . PlaneName ) ;
…但是现在,由于op希望该类型是另一个匿名类型的数组,因此无法引用它。
啊,这是一个很好的观点。那么,IEnumerable 可能会做到这一点,但它确实会清除任何编译时类型的知识。
是的,切换到IEnumerable 是可行的——但是我们可以在不更改测试代码的情况下修复测试代码;请参阅我的答案。
好主意!谢谢你,安德拉斯
有两件事发生了:
首先,当使用new { Name = Value} 构造匿名类型的实例时,为了构建该类型,编译器需要能够计算出Value 的类型。只是null 本身没有类型,所以编译器不知道要给Planes 成员什么类型。
现在,如果您正在使用一个命名类型作为值,您可以只说(type)null 并完成,但是因为您需要另一个匿名类型的数组,所以没有办法引用is(它是匿名的!).
那么,如何将null 类型化为匿名类型的数组呢?好吧,C规范保证匿名类型的成员具有相同的名称和类型(顺序相同!)是统一的;也就是说,如果我们说
1 2
var a
= new { Foo
= "Bar" } ;
var b
= new { Foo
= "Baz" } ;
那么,a 和b 有相同的类型。我们可以利用这一事实得到我们的适当类型的null ,因此:
1 2
var array
= ( new [ ] { new { PlaneName
= "" } } ) ;
array
= null ;
它不是很漂亮,但很管用——现在array 有了正确的类型,但null 的值。因此,这是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
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() 。
用default(Plane[]) 代替null 。