【C#】如何恢复匿名对象,但避免这么做

英文原文地址:

https://www.jacksondunstan.com/articles/3237

 当我们只需要一个又快又脏的类型来保存一些值时,C#的匿名类型就满足了我们的要求:

var person = { First="John", Last="Doe", Age=42 }.

 缺点是,因为这些类型是匿名的,所以它们没有显式类型。Var变量是强类型的,但在将它们传递给其他函数时必须使用Object类型。那你怎么才能把他转换回来呢?今天的文章将向您展示如何恢复匿名类型。
 让我们简单地从使用匿名类型开始:

void Start()
{
	var fullName = new {
		First = "Jackson",
		Last = "Dunstan"
	};
	var person = new {
		First = "Jackson",
		Last = "Dunstan",
		Location = "California"
	};
	Debug.Log(fullName.GetType());
	Debug.Log(person.GetType());
}

 这将打印出C#编译器为我们的匿名类型而自动生成的类的名称:

<>__AnonType0`2[System.String,System.String]
<>__AnonType1`3[System.String,System.String,System.String]

 正如我们所预期的那样,第一个类型有两个字符串属性,第二个类型有三个。但这引发了一个有趣的问题:如果另一个函数创建了具有相同字段的匿名类型实例,编译器是否会重用生成的类型?让我们试一试,看看:

void Start()
{
	// .. same as above
	CheckSameTypes(fullName.GetType(), person.GetType());
}
 
void CheckSameTypes(Type fullNameType, Type personType)
{
	// Same field names
	// Same field types
	// Different values
	// Different function
	var fullName = new
	{
		First = "John",
		Last = "Doe"
	};
	var person = new
	{
		First = "John",
		Last = "Doe",
		Location = "Anytown"
	};
 
	// Are they the same?
	Debug.Log(fullName.GetType() == fullNameType);
	Debug.Log(person.GetType() == personType);
}

 事实上,这两行打印的都是true。这意味着编译器即使在函数之间也会重用相同的生成类型。如果您在Unity项目中的另一个类中使用该类型,情况也是如此,但为了简洁起见,我将省略这一点。
这一发现为我们恢复强类型打开了大门,但首先让我们看看我们试图解决的问题。我们假设出一个想要打印名字和姓氏的简单函数:

void PrintFullName(object fullName)
{
	// compiler errors:
	Debug.Log(fullName.First + " " + fullName.Last);
}

 但是这不行,这会编译出错。

error CS1061: Type `object' does not contain a definition for `First' and no extension method `First' of type `object' could be found (are you missing a using directive or an assembly reference?)

 这是因为我们被迫在start函数中接受我们的强类型var局部变量,并将其作为对象类型的参数传递给PrintFullName。因为Object是C#中所有类型的根,所以它肯定不会有我们的First和Last字段!
 那么,我们如何恢复匿名类型,以便打印第一个和最后一个字段呢?我们显然需要将对象参数转换为具有这些字段的匿名类型,但该类型没有名称,因此我们需要找到另一种方式将其表达给编译器。幸运的是,我们发现用相同的字段名和类型实例化另一个会产生相同的生成的类类型,所以让我们从这里开始:

void PrintFullName(object fullName)
{	
var tempFullName = new { First = "", Last = "" };
	// compiler errors:	Debug.Log(fullName.First + " " + fullName.Last);
}

 现在,我们如何将Object转换为tempFullName具有的类型?带类型参数的帮助器函数可以做到这一点:

T Cast<T>(object obj, T type)
{	
	return (T)obj;
}

 此函数获取我们拥有的对象,并将其强制转换为T,T可以是我们想要的任何值。让我们使用PrintFullName中的helper函数来恢复类型化的对象:

void PrintFullName(object fullName)
{	
	var typed = Cast(fullName, new { First = "", Last = "" });	
	// compiler errors:	Debug.Log(fullName.First + " " + fullName.Last);
}

 现在,我们已经输入了一个var局部变量,其类型与调用方的匿名类型相同!最后一步是简单地使用它,而不是对象类型的参数:

void PrintFullName(object fullName)
{	
	var typed = Cast(fullName, new { First = "", Last = "" });	
	Debug.Log(typed .First + " " + typed .Last);
}

有两个重要的警告。

  • 首先,据我所知,不能保证编译器会对任何两个匿名类型实例使用相同的生成类。它只是碰巧在Unity的Mono编译器上是这样工作的。如果它发生了变化,并且您依赖于此行为,您的代码将会崩溃。

  • 其次,您仍在传递一个非类型化的对象参数并进行强制转换,因此不需要在编译时检查您传递的参数是否正确。例如,如果您尝试传递Person而不是fullName,您将得到一个运行时错误,因为编译器为它的三个字符串生成了一个不同的类。

 因此,可以认为这只是一个取巧的办法。当您需要将对象传递给其他函数时,通常应该使用手写的类和接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值