Unity记录使用Preserve特性防止反射调用代码在build时被裁剪
Posted mkr67n
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity记录使用Preserve特性防止反射调用代码在build时被裁剪相关的知识,希望对你有一定的参考价值。
本文内容
本文介绍:
- 构建时代码裁剪
- 裁剪存在的问题
- Preserve特性(
UnityEngine.Scripting.PreserveAttribute
)
自动代码裁剪(Managed code stripping)
在Edit->Project Settings->Player->(特定平台)Optimization->Managed Stripping Level中可以选择构建时代码裁剪的程度。
代码裁剪将在构建过程中删除未使用或不可访问的代码,以显著减少程序的最终大小。
该过程会尝试从:你脚本所生成的程序集、插件或包的程序集、.Net Framework程序集中裁剪不可达代码。
裁剪的问题
- 一般情况:代码引用都可在编译时确定,不会存在任何问题。
- 反射调用:但反射(
Reflection
)调用(借助字符串等动态读取数据生成对象或调用方法)无法在编译时追踪。
因此在提升裁剪程度时,被反射调用的代码可能会在裁剪过程中被移除。当尝试调用到这些不存在代码时,会出现各种各样令人意外的错误。
比如我所遇到的问题:
我使用某个JSON库进行序列化与反序列化保存游戏,当在Unity编辑器中,一切正常;当裁剪等级设置为High时,构建后加载存档产生如下错误:
可以归纳出:
- 这是一个反射相关的错误
- 涉及
Linq.Where()
函数的调用
当存在以下要素:
- 构建后运行存在反射错误
- 编辑器中运行一切正常
- 使用了构建裁剪
基本上可以断定是代码裁剪把某些重要代码给意外裁剪掉了。一般在Print出错误堆栈时,你可以在里面找到一些端倪(比如调用过程涉及的函数名或类名)。但我这个错误实属有点阴间,它的错误堆栈一共就只有两行。
但好歹给了些提示,涉及LInq.Where()
。仔细思考,JSON序列化库应该不会使用到Linq
(经过代码查找也确实如此),因此我把重心放在了我的脚本中。最后发现:
在我存档的序列化对象中,存在一处Linq.Where()
的调用。
但是下面的构造函数LevelData(LevelManager levelInfo)
是用于保存的;上面的无参构造函数LevelData()
才是用于加载的。
为什么在加载时,反射生成存档对象时没有匹配上面的无参构造函数,而是匹配了下面的有参函数?原因就是……无参构造函数压根就不存在于构建后的代码中。
可以看到,LevelData()
的引用个数是0,结合裁剪等级设置为High,基本可以断定,这个函数将在构建时移除。
Preserve特性
[Preserve]
可以显式声明在构建中保留的代码。可作用于:类、方法、字段或属性。
以下是使用该特性解决上述问题的例子:
using UnityEngine.Scripting;
public class LevelData
......
[Preserve]
public LevelData() //构造函数将一定保留
受这个特性修饰的所有单位将在裁剪时无条件保留。我的问题也在相关代码加入了[Preserve]
修饰后解决。
总结
吾日三省吾身:
- 有使用代码裁剪乎?
- 代码存在反射调用乎?
- Preserve乎?
C#中的反射和特性
C#中的反射和特性(一)
前言:
前段时间将Unity3d中UGUI组件精简复盘落下了帷幕,后期博主可能会从设计模式,部分的算法,以及从UGUI实际操作上、或者从Unity3D部分的一些小游戏Demo上去做文章。但是在此之前,还是多多了解和使用使用C#吧,这就是我之前提到了框架,lua的框架在便捷的同时,也会增加一个人的编程语言的惰性,虽然不是不懂,但是久而久之,使用少了,也就没有那么熟练了。所以近期可能会更多的从C#或者Unity的相关小型Demo的讲解入手去练习与讲解。
话不多说,我们直接进入正题~ 今天我们讲的就是C#中的反射和特性(一)啦~
1.什么是元数据,什么是反射
程序是用来处理数据的,文本和特性都是数据,而我们程序本身(类的定义和BLC中的类)这些也是数据。
有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中。
程序在运行时,可以查看其它程序集或其本身的元数据。一个运行的程序查看本身的元数据或者其他程序集的元数据的行为叫做反射。
2.Type类
预定义类型(int long 和string等),BCL中的类型(Console,IEnumerable等)和程序员自定义类型(MyClass,MyDel等)。 每种类型都有自己的成员和特性。
BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。
由于 Type是抽象类,因此不能利用它去实例化对象。关于Type的重要事项如下:
对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象。
程序中用到的每一个类型都会关联到独立的Type类的对象。
不管创建的类型有多少个示例,只有一个Type对象会关联到所有这些实例
3.System.Type类部分成员
成员 成员类型 描述
Name 属性 返回类型的名字
Namespace 属性 返回包含类型声明的命名空间
Assembly 属性 返回声明类型的程序集。
GetFields 方法 返回类型的字段列表
GetProperties 方法 返回类型的属性列表
GetMethods 方法 返回类型的方法列表
那就举个例子吧:
我们定义一个Myclass类,并且定义好相关的字段,属性以及方法,我们就通过反射的原理来了解他们实际上代表的意义吧
Myclass.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test_Reflection_and_characteristic
public class Myclass
private int id;
private int age;
public int height;
public int weight;
private string Sex get; set;
public string Name get; set;
public void Test()
public void Test2()
private void Test3()
在主程序中我们通过反射的关系去拿到这个Myclass类的相关信息
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Test_Reflection_and_characteristic
class Program
static void Main(string[] args)
Myclass my = new Myclass();//每一个类对应一个type对象,这个type对象存储了这个类,有哪些方法跟哪些方法以及哪些成员
Type testType = my.GetType();//一个类中的数据是存储在对象中的,但是type对象只存储类的成员
Console.WriteLine("输出 testType所引用的命名空间:0", testType);
Console.WriteLine("获取当前testType所引用的类名:0", testType.Name);
Console.WriteLine("输出当前tesType中的程序集:0", testType.Assembly);
Console.WriteLine("输出与当前testType相关联的属性:0", testType.Attributes);
输出结果:
我想通过这种方式输出表达出来已经足够明确了吧
同时当我们想要通过反射去访问到类里面的类的字段、属性与方法也是可以的
在主程序中补充代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Test_Reflection_and_characteristic
class Program
static void Main(string[] args)
Myclass my = new Myclass();//每一个类对应一个type对象,这个type对象存储了这个类,有哪些方法跟哪些方法以及哪些成员
Type testType = my.GetType();//一个类中的数据是存储在对象中的,但是type对象只存储类的成员
Console.WriteLine("输出 testType所引用的命名空间:0", testType);
Console.WriteLine("获取当前testType所引用的类名:0", testType.Name);
Console.WriteLine("输出当前tesType中的程序集:0", testType.Assembly);
Console.WriteLine("输出与当前testType相关联的属性:0", testType.Attributes);
//通过FieldInfo去访问字段
FieldInfo[] array = testType.GetFields();
foreach (FieldInfo info in array)
//注意只有public公有的字段才能访问到,private私有字段是无法访问到的
Console.WriteLine("输出当前testType中所含有的字段0", info.Name);
PropertyInfo[] array2 = testType.GetProperties();
foreach (PropertyInfo info in array2)
//注意只有public公有的属性才能访问到,private私有属性是无法访问到的
Console.WriteLine("输出当前testType中所含有的属性0", info.Name);
MethodInfo[] array3 = testType.GetMethods();
foreach (MethodInfo info in array3)
//注意只有public公有的方法才能访问到,private私有方法是无法访问到的
Console.WriteLine("输出当前testType中所含有的方法0", info.Name);
编译结果:
注意:无论是类的字段和属性以及方法,都是只能访问到public权限下的成员与数据。后面的Equals方法,GetHashCode以及GetType、Tostring方法是Type类里面自己重写的。想要了解的话,直接找到主程序中的Type,按下F12键就知道啦
如图所示:
“哥哥”的快乐你是体会不到的!自己可以慢慢按F12去体会其中的快乐奥,哥哥累了,哥哥要休息一小会儿…。
如果博客博友对这个章节有更多的内容想要了解的,可以评论哈,等这一个章节结束后,我会根据评论反馈以及自己的能力,将评论中反馈的内容在下一个计划中去制作出对应的内容的哈~
作者:ProMer_Wang
链接:https://blog.csdn.net/qq_43801020/article/details/120245915
本文为ProMer_Wang的原创文章,著作权归作者所有,转载请注明原文出处,欢迎转载!
以上是关于Unity记录使用Preserve特性防止反射调用代码在build时被裁剪的主要内容,如果未能解决你的问题,请参考以下文章