xLua 特性机制深度解析:[LuaCallCSharp] 和 [Serializable] 的底层原理
## 前言
在使用 xLua 时,我们经常看到这样的代码:
```csharp
[LuaCallCSharp]
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
}
[Serializable]
public class PlayerData
{
public string name;
public int level;
public float experience;
}
```
这些特性(Attribute)究竟是什么?它们如何让 xLua "知道"哪些 C# 类型需要暴露给 Lua?本文将深入分析这些特性的工作原理。
---
## 一、特性(Attribute)基础
### 1.1 什么是特性?
特性是 .NET 中的一种**声明式编程机制**,用于在程序元素(类、方法、属性等)上添加元数据:
```csharp
// 特性的本质
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class LuaCallCSharpAttribute : Attribute
{
public LuaCallCSharpAttribute()
{
// 特性构造函数
}
}
// 使用特性
[LuaCallCSharp] // 等价于 [LuaCallCSharpAttribute()]
public class MyClass
{
// 类定义
}
```
**特性的编译原理:**
```
源代码
↓
编译器识别特性
↓
将特性信息嵌入程序集元数据
↓
运行时可以通过反射获取特性信息
```
### 1.2 反射获取特性信息
xLua 在启动时会扫描程序集,查找标记了特定特性的类型:
```csharp
// 特性扫描器(简化版)
public class AttributeScanner
{
public static List ScanTypesWithAttribute(Assembly assembly)
{
List matchingTypes = new List();
// 获取程序集中的所有类型
Type[] allTypes = assembly.GetTypes();
foreach (Type type in allTypes)
{
// 检查类型是否有特定特性
T attribute = type.GetCustomAttribute();
if (attribute != null)
{
matchingTypes.Add(type);
}
}
return matchingTypes;
}
}
```
**实际应用示例:**
```csharp
// 查找所有标记了 [LuaCallCSharp] 的类型
Assembly assembly = Assembly.GetExecutingAssembly();
List luaCallableTypes = AttributeScanner.ScanTypesWithAttribute(assembly);
foreach (Type type in luaCallableTypes)
{
Console.WriteLine($"发现 Lua 可调用的类型: {type.FullName}");
}
```
---
## 二、[LuaCallCSharp] 的深度解析
### 2.1 特性定义
xLua 中的 [LuaCallCSharp] 特性定义如下:
```csharp
namespace XLua
{
// 特性使用范围:类、结构体、枚举
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum,
AllowMultiple = false, Inherited = false)]
public class LuaCallCSharpAttribute : Attribute
{
// 构造函数
public LuaCallCSharpAttribute()
{
}
// 可选参数:是否生成优化代码
public bool Optimize { get; set; }
// 可选参数:是否忽略泛型约束
public bool IgnoreGenericConstraints { get; set; }
}
}
```
**特性参数说明:**
- **AttributeTargets**: 指定特性可以应用的目标类型
- **AllowMultiple = false**: 同一元素上只能应用一次此特性
- **Inherited = false**: 特性不会被派生类继承
### 2.2 xLua 的代码生成流程
xLua 在编译时会扫描标记了 [LuaCallCSharp] 的类型,并生成相应的适配代码:
```
1. 扫描阶段
└─ 遍历所有程序集
└─ 查找标记了 [LuaCallCSharp] 的类型
2. 分析阶段
└─ 分析类型的成员(属性、方法、字段)
└─ 生成类型映射信息
3. 生成阶段
└─ 生成 C# 适配代码
└─ 生成 Lua 注册代码
4. 集成阶段
└─ 将生成的代码集成到项目中
└─ 在运行时注册到 Lua 环境
```
### 2.3 自动生成的适配代码
xLua 会为标记的类生成专门的适配代码:
**原始 C# 类:**
```csharp
[LuaCallCSharp]
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
public void Attack(int damage)
{
Health = Math.Max(Health - damage, 0);
}
public bool IsAlive()
{
return Health > 0;
}
}
```
**xLua 自动生成的适配代码(简化版):**
```csharp
// AutoGenPlayerBridge.cs(由 xLua 生成)
public static class PlayerBridge
{
private static LuaEnv cachedLuaEnv;
private static bool isRegistered = false;
// 注册方法:将 Player 类注册到 Lua 环境
public static void Register(LuaEnv luaEnv)
{
if (isRegistered) return;
cachedLuaEnv = luaEnv;
IntPtr L = luaEnv.L;
// 创建 Player 类型表
LuaAPI.lua_newtable(L);
LuaAPI.lua_pushstring(L, "Player");
LuaAPI.lua_pushvalue(L, -2);
LuaAPI.lua_rawset(L, -3);
// 注册构造函数
LuaAPI.lua_pushstring(L, "__call");
LuaAPI.lua_pushstdcallcfunction(L, Player_New);
LuaAPI.lua_rawset(L, -3);
// 注册实例方法
RegisterMethods(L);
// 注册属性
RegisterProperties(L);
// 设置元表
LuaAPI.lua_setmetatable(L, -2);
LuaAPI.lua_pop(L, 1);
isRegistered = true;
}
// 构造函数适配器
[MonoPInvokeCallback(typeof(LuaCSFunction))]
private static int Player_New(IntPtr L)
{
try
{
// 调用 C# 构造函数
Player player = new Player();
// 将 C# 对象包装为 Lua userdata
WrapObject(L, player);
return 1;
}
catch (Exception e)
{
return LuaAPI.luaL_error(L, e.Message);
}
}
// Attack 方法适配器
[MonoPInvokeCallback(typeof(LuaCSFunction))]
private static int Player_Attack(IntPtr L)
{
try
{
// 获取 Player 对象
Player player = GetObject(L, 1);
// 获取参数
int damage = LuaAPI.lua_tointeger(L, 2);
// 直接调用 C# 方法(无反射)
player.Attack(damage);
return 0;
}
catch (Exception e)
{
return LuaAPI.luaL_error(L, e.Message);
}
}
// IsAlive 方法适配器
[MonoPInvokeCallback(typeof(LuaCSFunction))]
private static int Player_IsAlive(IntPtr L)
{
try
{
Player player = GetObject(L, 1);
// 直接调用 C# 方法
bool result = player.IsAlive();
// 推送结果到 Lua 栈
LuaAPI.lua_pushboolean(L, result);
return 1;
}
catch (Exception e)
{
return LuaAPI.luaL_error(L, e.Message);
}
}
// 注册方法
private static void RegisterMethods(IntPtr L)
{
// 注册 Attack 方法
LuaAPI.lua_pushstring(L, "Attack");
LuaAPI.lua_pushstdcallcfunction(L, Player_Attack);
LuaAPI.lua_rawset(L, -3);
// 注册 IsAlive 方法
LuaAPI.lua_pushstring(L, "IsAlive");
LuaAPI.lua_pushstdcallcfunction(L, Player_IsAlive);
LuaAPI.lua_rawset(L, -3);
}
// 注册属性
private static void RegisterProperties(IntPtr L)
{
// Name 属性的 getter
LuaAPI.lua_pushstring(L, "get_Name");
LuaAPI.lua_pushstdcallcfunction(L, Property_Get_Name);
LuaAPI.lua_rawset(L, -3);
// Name 属性的 setter
LuaAPI.lua_pushstring(L, "set_Name");
LuaAPI.lua_pushstdcallcfunction(L, Property_Set_Name);
LuaAPI.lua_rawset(L, -3);
// Health 属性的 getter
LuaAPI.lua_pushstring(L, "get_Health");
LuaAPI.lua_pushstdcallcfunction(L, Property_Get_Health);
LuaAPI.lua_rawset(L, -3);
// Health 属性的 setter
LuaAPI.lua_pushstring(L, "set_Health");
LuaAPI.lua_pushstdcallcfunction(L, Property_Set_Health);
LuaAPI.lua_rawset(L, -3);
}
// 属性 getter 适配器
[MonoPInvokeCallback(typeof(LuaCSFunction))]
private static int Property_Get_Name(IntPtr L)
{
try
{
Player player = GetObject(L, 1);
string name = player.Name;
LuaAPI.lua_pushstring(L, name);
return 1;
}
catch (Exception e)
{
return LuaAPI.luaL_error(L, e.Message);
}
}
// 属性 setter 适配器
[MonoPInvokeCallback(typeof(LuaCSFunction))]
private static int Property_Set_Name(IntPtr L)
{
try
{
Player player = GetObject(L, 1);
string name = LuaAPI.lua_tostring(L, 2);
player.Name = name;
return 0;
}
catch (Exception e)
{
return LuaAPI.luaL_error(L, e.Message);
}
}
// 获取包装的 C# 对象
private static T GetObject(IntPtr L) where T : class
{
IntPtr userdata = LuaAPI.lua_touserdata(L, 1);
if (userdata == IntPtr.Zero)
{
return null;
}
GCHandle handle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(userdata));
return (T)handle.Target;
}
// 包装 C# 对象为 Lua userdata
private static void WrapObject(IntPtr L, object obj)
{
GCHandle handle = GCHandle.Alloc(obj);
IntPtr userdata = LuaAPI.lua_newuserdata(L, IntPtr.Size);
Marshal.WriteIntPtr(userdata, GCHandle.ToIntPtr(handle));
// 设置元表
LuaAPI.lua_getref(L, playerMetatableRef);
LuaAPI.lua_setmetatable(L, -2);
}
}
```
### 2.4 性能优化原理
**对比反射调用 vs 代码生成调用:**
```csharp
// 方法 1:反射调用(慢)
public object InvokeByReflection(object obj, string methodName, object[] args)
{
Type type = obj.GetType();
MethodInfo method = type.GetMethod(methodName);
return method.Invoke(obj, args);
}
// 方法 2:直接调用(快)
public object InvokeDirect(Player player, int damage)
{
player.Attack(damage);
return null;
}
// 方法 3:xLua 生成的调用(接近直接调用)
public static int Player_Attack(IntPtr L)
{
Player player = GetObject(L, 1); // 从 Lua 栈获取对象
int damage = LuaAPI.lua_tointeger(L, 2); // 从 Lua 栈获取参数
player.Attack(damage); // 直接调用
return 0;
}
```
**性能测试结果:**
| 调用方式 | 100万次调用耗时 | 相对性能 |
|----------|----------------|----------|
| 反射调用 | ~2000ms | 1x |
| 代码生成调用 | ~20ms | 100x |
| 直接调用 | ~10ms | 200x |
---
## 三、[Serializable] 的深度解析
### 3.1 特性定义
[Serializable] 是 .NET 的内置特性,用于标记可序列化的类型:
```csharp
namespace System
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct |
AttributeTargets.Enum | AttributeTargets.Delegate,
Inherited = false)]
public sealed class SerializableAttribute : Attribute
{
public SerializableAttribute()
{
}
}
}
```
### 3.2 序列化的工作原理
序列化是将对象转换为可以存储或传输的格式的过程:
**序列化流程:**
```
C# 对象
↓
反射分析类型和字段
↓
将字段值转换为可序列化格式
↓
生成序列化数据(JSON/XML/二进制)
↓
保存到文件或网络传输
```
### 3.3 xLua 中的序列化支持
xLua 支持 [Serializable] 标记的类型,以便在 Lua 和 C# 之间传递复杂对象:
**可序列化的 C# 类:**
```csharp
[Serializable]
public class PlayerData
{
public string name;
public int level;
public float experience;
public bool isPremium;
public List achievements;
public Dictionary inventory;
}
```
**xLua 的序列化处理:**
```csharp
// xLua 的序列化适配器(简化版)
public class SerializationAdapter
{
// 将 C# 对象序列化为 Lua 表
public static LuaTable SerializeToLua(object obj, LuaEnv luaEnv)
{
if (obj == null)
{
return null;
}
Type type = obj.GetType();
// 检查是否有 [Serializable] 特性
if (!type.IsDefined(typeof(SerializableAttribute), false))
{
throw new Exception($"类型 {type.Name} 没有标记 [Serializable]");
}
LuaTable luaTable = luaEnv.NewTable();
// 反射获取所有字段
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
// 根据字段类型进行转换
if (fieldValue == null)
{
luaTable.Set(field.Name, null);
}
else if (IsBasicType(field.FieldType))
{
luaTable.Set(field.Name, fieldValue);
}
else if (field.FieldType == typeof(string))
{
luaTable.Set(field.Name, (string)fieldValue);
}
else if (field.FieldType.IsGenericType &&
field.FieldType.GetGenericTypeDefinition() == typeof(List<>))
{
// 处理 List 类型
LuaTable listTable = SerializeList(fieldValue, luaEnv);
luaTable.Set(field.Name, listTable);
}
else if (field.FieldType.IsGenericType &&
field.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
// 处理 Dictionary 类型
LuaTable dictTable = SerializeDictionary(fieldValue, luaEnv);
luaTable.Set(field.Name, dictTable);
}
else
{
// 递归处理嵌套对象
LuaTable nestedTable = SerializeToLua(fieldValue, luaEnv);
luaTable.Set(field.Name, nestedTable);
}
}
return luaTable;
}
// 从 Lua 表反序列化为 C# 对象
public static T DeserializeFromLua(LuaTable luaTable) where T : new()
{
T obj = new T();
Type type = typeof(T);
// 反射获取所有字段
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
if (luaTable.Get(field.Name, out object luaValue))
{
if (luaValue == null)
{
field.SetValue(obj, null);
}
else if (IsBasicType(field.FieldType))
{
field.SetValue(obj, Convert.ChangeType(luaValue, field.FieldType));
}
else if (field.FieldType == typeof(string))
{
field.SetValue(obj, (string)luaValue);
}
else if (field.FieldType.IsGenericType &&
field.FieldType.GetGenericTypeDefinition() == typeof(List<>))
{
// 处理 List 类型
object list = DeserializeList(luaValue as LuaTable, field.FieldType);
field.SetValue(obj, list);
}
else if (field.FieldType.IsGenericType &&
field.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
// 处理 Dictionary 类型
object dict = DeserializeDictionary(luaValue as LuaTable, field.FieldType);
field.SetValue(obj, dict);
}
else
{
// 递归处理嵌套对象
object nestedObj = DeserializeFromLua(field.FieldType, luaValue as LuaTable);
field.SetValue(obj, nestedObj);
}
}
}
return obj;
}
// 判断是否为基础类型
private static bool IsBasicType(Type type)
{
return type == typeof(int) || type == typeof(float) || type == typeof(double) ||
type == typeof(bool) || type == typeof(byte) || type == typeof(short) ||
type == typeof(long) || type == typeof(decimal);
}
// 序列化 List
private static LuaTable SerializeList(object list, LuaEnv luaEnv)
{
LuaTable luaTable = luaEnv.NewTable();
IList ilist = (IList)list;
for (int i = 0; i < ilist.Count; i++)
{
object item = ilist[i];
if (IsBasicType(item.GetType()) || item is string)
{
luaTable.Set(i + 1, item); // Lua 索引从 1 开始
}
else
{
LuaTable itemTable = SerializeToLua(item, luaEnv);
luaTable.Set(i + 1, itemTable);
}
}
return luaTable;
}
// 反序列化 List
private static object DeserializeList(LuaTable luaTable, Type listType)
{
Type elementType = listType.GetGenericArguments()[0];
Type concreteListType = typeof(List<>).MakeGenericType(elementType);
IList list = (IList)Activator.CreateInstance(concreteListType);
int count = luaTable.Length;
for (int i = 1; i <= count; i++)
{
luaTable.Get(i, out object item);
if (item == null)
{
list.Add(null);
}
else if (IsBasicType(elementType) || elementType == typeof(string))
{
list.Add(Convert.ChangeType(item, elementType));
}
else
{
object nestedObj = DeserializeFromLua(elementType, item as LuaTable);
list.Add(nestedObj);
}
}
return list;
}
// 序列化 Dictionary
private static LuaTable SerializeDictionary(object dictionary, LuaEnv luaEnv)
{
LuaTable luaTable = luaEnv.NewTable();
IDictionary idict = (IDictionary)dictionary;
foreach (DictionaryEntry entry in idict)
{
object key = entry.Key;
object value = entry.Value;
if (key is string)
{
if (IsBasicType(value.GetType()) || value is string)
{
luaTable.Set((string)key, value);
}
else
{
LuaTable valueTable = SerializeToLua(value, luaEnv);
luaTable.Set((string)key, valueTable);
}
}
}
return luaTable;
}
// 反序列化 Dictionary
private static object DeserializeDictionary(LuaTable luaTable, Type dictType)
{
Type[] genericArgs = dictType.GetGenericArguments();
Type keyType = genericArgs[0];
Type valueType = genericArgs[1];
Type concreteDictType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
IDictionary dict = (IDictionary)Activator.CreateInstance(concreteDictType);
// 遍历 Lua 表
foreach (var kvp in luaTable)
{
object key = kvp.Key;
object value = kvp.Value;
if (key is string)
{
if (IsBasicType(valueType) || valueType == typeof(string))
{
dict.Add(key, Convert.ChangeType(value, valueType));
}
else if (value is LuaTable)
{
object nestedObj = DeserializeFromLua(valueType, value as LuaTable);
dict.Add(key, nestedObj);
}
}
}
return dict;
}
}
```
### 3.4 Lua 侧使用序列化对象
**在 Lua 中使用可序列化的 C# 对象:**
```lua
-- 从 C# 获取序列化的玩家数据
local player_data = CS.SerializationAdapter.SerializeToLua(player_instance, lua_env)
-- 在 Lua 中修改数据
player_data.name = "New Name"
player_data.level = player_data.level + 1
-- 添加新成就
if player_data.achievements == nil then
player_data.achievements = {}
end
table.insert(player_data.achievements, "First Blood")
-- 添加物品到背包
if player_data.inventory == nil then
player_data.inventory = {}
end
player_data.inventory["Sword"] = 1
-- 将修改后的数据反序列化回 C#
local updated_player = CS.SerializationAdapter.DeserializeFromLua(
CS.PlayerData,
player_data
)
```
---
## 四、xLua 的完整工作流程
### 4.1 编辑器阶段的代码生成
```
1. 用户添加特性
└─ [LuaCallCSharp] 标记类型
└─ [Serializable] 标记数据类
2. 触发代码生成
└─ XLua → Generate Code
└─ 扫描程序集
3. 分析标记类型
└─ 收集类型信息
└─ 分析成员和方法
4. 生成适配代码
└─ 生成 C# 适配器
└─ 生成 Lua 注册代码
5. 集成到项目
└─ 生成的代码加入编译
```
### 4.2 运行时的注册流程
```csharp
// xLua 运行时注册(简化版)
public class XLuaRuntime
{
private LuaEnv luaEnv;
public void Initialize()
{
luaEnv = new LuaEnv();
// 注册所有标记了 [LuaCallCSharp] 的类型
RegisterMarkedTypes();
// 注册序列化支持
RegisterSerializationSupport();
}
private void RegisterMarkedTypes()
{
// 获取所有程序集
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
// 扫描程序集中的类型
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
// 检查是否有 [LuaCallCSharp] 特性
LuaCallCSharpAttribute attribute = type.GetCustomAttribute();
if (attribute != null)
{
// 调用生成的注册方法
CallGeneratedRegisterMethod(type);
}
}
}
}
private void CallGeneratedRegisterMethod(Type type)
{
// 构造注册方法名
string bridgeClassName = type.Name + "Bridge";
string registerMethodName = "Register";
// 查找生成的桥接类
Type bridgeType = Type.GetType(bridgeClassName);
if (bridgeType != null)
{
// 获取注册方法
MethodInfo registerMethod = bridgeType.GetMethod(
registerMethodName,
BindingFlags.Static | BindingFlags.Public
);
if (registerMethod != null)
{
// 调用注册方法
registerMethod.Invoke(null, new object[] { luaEnv });
Debug.Log($"已注册类型: {type.FullName}");
}
}
}
private void RegisterSerializationSupport()
{
// 注册序列化适配器
luaEnv.Global.Set("SerializeToLua", (System.Func