It's an implementation detail that you don't understand.
If you use an anonymous type, the compiler has to generate a new type (with an unspeakable name, such as <>f__AnonymousType0<<A>j__TPar>
), and it generates this type in the assembly which uses it.
It will use that same generated type for all usages of anonymous types with the same structure within that assembly. However, each assembly will have its own anonymous type definitions: there's no way to share them across assemblies. Of course the way around this, as you discovered, is to pass them around as object
.
This restriction is one of the main reasons why there's no way of exposing anonymous types: you can't return them from methods, have them as fields etc. It would cause all sorts of issues if you could pass them around between assemblies.
You can see that at work in SharpLab, where:
var x = new { A = 1 };
causes this type to be generated in the same assembly:
internal sealed class <>f__AnonymousType0<<A>j__TPar>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly <A>j__TPar <A>i__Field;
public <A>j__TPar A
{
get
{
return <A>i__Field;
}
}
[DebuggerHidden]
public <>f__AnonymousType0(<A>j__TPar A)
{
<A>i__Field = A;
}
[DebuggerHidden]
public override bool Equals(object value)
{
global::<>f__AnonymousType0<<A>j__TPar> anon = value as global::<>f__AnonymousType0<<A>j__TPar>;
if (anon != null)
{
return EqualityComparer<<A>j__TPar>.Default.Equals(<A>i__Field, anon.<A>i__Field);
}
return false;
}
[DebuggerHidden]
public override int GetHashCode()
{
return -1711670909 * -1521134295 + EqualityComparer<<A>j__TPar>.Default.GetHashCode(<A>i__Field);
}
[DebuggerHidden]
public override string ToString()
{
object[] obj = new object[1];
<A>j__TPar val = <A>i__Field;
obj[0] = ((val != null) ? val.ToString() : null);
return string.Format(null, "{{ A = {0} }}", obj);
}
}
ValueTuple
had the same challenges around wanting to define types anonymously but still pass them between assemblies, and solved it a different way: by defining ValueTuple<..>
in the BCL, and using compiler magic to pretend that their properties have names other than Item1
, Item2
, etc.