手写一个C#文件解析(命名空间+类名)
开发环境:VS2013+.Net2.0
Unity版本:5.3.7
XLua版本:v2.1.10
运行环境:Windows10
因为项目使用XLua做热更,需要在代码中指定哪些类可以打补丁,文档介绍:
## 标识要热更新的类型
和其它配置一样,有两种方式
方式一:直接在类里头打Hotfix标签;
方式二:在一个static类的static字段或者属性里头配置一个列表。属性可以用于实现的比较复杂的配置,比如根据Namespace做白名单。
~~~csharp
public static class HotfixCfg
{
[Hotfix]
public static List<Type> by_field = new List<Type>()
{
typeof(HotFixSubClass),
typeof(GenericClass<>),
};
[Hotfix]
public static List<Type> by_property
{
get
{
return (from type in Assembly.Load("Assembly-CSharp").GetTypes()
where type.Namespace == "XXXX"
select type).ToList();
}
}
}
想把项目中大多数自定义的类添加Hotfix标签,又不想一个类一个类去查找,前期如果划分好命名空间,也比较方便的打标签,但是这是半路接手的项目,没办法,所以想找个把代码文件转成代码结构的工具,而 把C#代码文件解析为CodeCompileUnit 这个的研究就是想解决这个问题,但是Unity不支持CodeCompileUnit,于是决定自己写一个解析,目前只需要命名空间+类名,所以解析也只做到这一步,其他如果有需要方法名什么的,再补充吧
Dome代码比较凌乱,直接用VS2013创建一个控制台程序就能跑,里面包括两种解析方式,结果都一样,一种是逐字符解析Parse1,一种是关键信息解析Parse2:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ConsoleApplication1
class Program
static void Main(string[] args)
List<string> allFullClassNameList = new List<string>();
string[] csFilePaths = Directory.GetFiles("Scripts", "*.cs", SearchOption.AllDirectories);
for (int i = 0; i < csFilePaths.Length; i++)
var csFilePath = csFilePaths[i];
Console.WriteLine(string.Format("{0}/{1} {2}", i, csFilePaths.Length, csFilePath));
string code = File.ReadAllText(csFilePath);
CodeStructure cs = CodeStructureHelper.Parse2(code);
//转成完整类名
List<string> fullClassNameList = CodeStructureHelper.GetFullClassNameList(cs);
allFullClassNameList.AddRange(fullClassNameList);
File.WriteAllLines("allFullClassNames.txt", allFullClassNameList.ToArray());
/// <summary>
/// C#代码文档结构
/// </summary>
public class CodeStructureHelper
/// <summary>
/// 获得完整类名
/// </summary>
/// <param name="codeStructure"></param>
/// <returns></returns>
public static List<string> GetFullClassNameList(CodeStructure codeStructure)
List<string> fullClassNameList = new List<string>();
//命名空间
foreach (var codeNamespace in codeStructure.CodeNamespaceList)
GetFullClassNameList(ref fullClassNameList, "", codeNamespace);
foreach (var codeClass in codeStructure.CodeClassList)
StringBuilder parentName = new StringBuilder();
GetFullClassNameList(ref fullClassNameList, "", codeClass);
return fullClassNameList;
public static void GetFullClassNameList(ref List<string> fullClassNameList, string parentName, CodeNamespace codeNamespace)
if (codeNamespace.InternalCodeNamespaceList.Count > 0
|| codeNamespace.CodeClassesList.Count > 0)
parentName = splicingParentName(parentName, codeNamespace.Name);
//子命名空间
foreach (var child in codeNamespace.InternalCodeNamespaceList)
GetFullClassNameList(ref fullClassNameList, parentName, child);
//该命名空间下的类
foreach (var child in codeNamespace.CodeClassesList)
GetFullClassNameList(ref fullClassNameList, parentName, child);
public static void GetFullClassNameList(ref List<string> fullClassNameList, string parentName, CodeClass codeClass)
string fullClassName = splicingParentName(parentName, codeClass.Name);
fullClassNameList.Add(fullClassName);
//内部类
if (codeClass.InternalCodeClassList.Count > 0)
foreach (var child in codeClass.InternalCodeClassList)
GetFullClassNameList(ref fullClassNameList, fullClassName, child);
//拼接父节点名字
private static string splicingParentName(string parentName, string name)
if (string.IsNullOrEmpty(parentName))
return name;
return string.Format("{0}.{1}", parentName, name);
#region 域判断文字
/// <summary>
/// 右斜杠
/// </summary>
public const char RightSlashStr = '\\';
/// <summary>
/// 双斜杠注释
/// </summary>
public const string CommentsStr = "//";
/// <summary>
/// 星号注释开始
/// </summary>
public const string BeginStarCommentsStr = "/*";
/// <summary>
/// 星号注释结束
/// </summary>
public const string EndStarCommentsStr = "*/";
/// <summary>
/// 开始大括号
/// </summary>
public const string BeginBigParanthesesStr = "{";
/// <summary>
/// 结束大括号
/// </summary>
public const string EndBigParanthesesStr = "}";
/// <summary>
/// 双引号
/// </summary>
public const string DoubleQuotationMarksStr = "\"";
/// <summary>
/// 单引号
/// </summary>
public const string ApostropheStr = "\'";
/// <summary>
/// 换行符
/// </summary>
public static readonly string NewlineChar_R = cNewlineChar_R.ToString();
/// <summary>
/// 换行符
/// </summary>
public static readonly string NewlineChar_N = cNewlineChar_N.ToString();
/// <summary>
/// 冒号
/// </summary>
public const string Colon = ":";
/// <summary>
/// 换行符
/// </summary>
public const char cNewlineChar_R = '\r';
/// <summary>
/// 换行符
/// </summary>
public const char cNewlineChar_N = '\n';
/// <summary>
/// 空格
/// </summary>
public const char cSpace = ' ';
/// <summary>
/// 制表符
/// </summary>
public const char cTabs = '\t';
#endregion
#region 关键字
public const string KeyWord_Namespace = "namespace";
public const string KeyWord_Class = "class";
#endregion
#region Parse2
/// <summary>
/// 使用关键信息查找,然后处理
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public static CodeStructure Parse2(string code)
CodeStructure cs = new CodeStructure();
if (string.IsNullOrEmpty(code))
return cs;
//记录作用域, 后进先出
List<CodeScope> codeScopeList = new List<CodeScope>();
int readIndex = 0;
List<FindIndex> findIndexList = new List<FindIndex>();
findIndexList.Add(new FindIndex(HandleCodeType.Namespace, KeyWord_Namespace));
findIndexList.Add(new FindIndex(HandleCodeType.Class, KeyWord_Class));
findIndexList.Add(new FindIndex(HandleCodeType.Comments, CommentsStr));
findIndexList.Add(new FindIndex(HandleCodeType.BeginStarComments, BeginStarCommentsStr));
findIndexList.Add(new FindIndex(HandleCodeType.BeginBigParantheses, BeginBigParanthesesStr));
findIndexList.Add(new FindIndex(HandleCodeType.EndBigParantheses, EndBigParanthesesStr));
findIndexList.Add(new FindIndex(HandleCodeType.DoubleQuotationMarks, DoubleQuotationMarksStr));
findIndexList.Add(new FindIndex(HandleCodeType.Apostrophe, ApostropheStr));
bool isHandle = false;
while (true)
isHandle = false;
//查找下标
for (int i = 0; i < findIndexList.Count; i++)
findIndexList[i].Find(code, readIndex);
//根据下标排序
findIndexList.Sort(handleOnFindIndexSort);
//查找除-1外最小的值处理
for (int i = 0; i < findIndexList.Count; i++)
FindIndex findIndex = findIndexList[i];
if (findIndex.Index < 0)
continue;
readIndex = handleCode(findIndex.HandleCodeType, findIndex.Index, code, cs, codeScopeList);
isHandle = true;
break;
if (!isHandle)
break;
if (readIndex < 0)
break;
return cs;
public class FindIndex
public HandleCodeType HandleCodeType;
public string FindStr;
private int index = -1;
public int Index { get { return index; } }
private bool noFind;
public FindIndex(HandleCodeType handleCodeType, string findStr)
this.HandleCodeType = handleCodeType;
this.FindStr = findStr;
public void Find(string code, int startIndex)
if (noFind)
{//未找到 不用继续找了
return;
if (startIndex > Index)
{//重新查找
index = code.IndexOf(FindStr, startIndex);
noFind = index == -1;
private static int handleOnFindIndexSort(FindIndex x, FindIndex y)
return x.Index - y.Index;
#endregion
//处理代码
private static int handleCode(HandleCodeType handleCodeType, int matchIndex, string code, CodeStructure cs, List<CodeScope> codeScopeList)
int readIndex = -1;
switch (handleCodeType)
case HandleCodeType.Namespace:
if (isKeyWord(matchIndex, code, KeyWord_Namespace))
matchIndex = skipStr(matchIndex, KeyWord_Namespace);
matchIndex++;//跳过一个空格
//找到域开始
int startBigParanthesesStrIndex = code.IndexOf(BeginBigParanthesesStr, matchIndex);
if (startBigParanthesesStrIndex < 0)
break;
string[] nameStrs = code.Substring(matchIndex, startBigParanthesesStrIndex - matchIndex).Trim().Split(new string[] { NewlineChar_R, NewlineChar_N }, StringSplitOptions.RemoveEmptyEntries);
if (nameStrs.Length == 0)
break;
string name = nameStrs[0];
//开启一个新命名空间记录
CodeNamespace codeNamespace = new CodeNamespace();
codeNamespace.Name = name;
CodeScope temp = new CodeScope();
temp.ScopeType = CodeScopeType.Namespace;
temp.Name = name;
temp.TargetObj = codeNamespace;
//放到对应的命名空间
CodeScope parentCodeScope = getLastCodeScopeByCodeScopeType(codeScopeList, CodeScopeType.Namespace);
if (parentCodeScope == null)
{//顶级命名空间域
cs.CodeNamespaceList.Add(codeNamespace);
CodeNamespace parentCodeNamespace = (CodeNamespace)parentCodeScope.TargetObj;
parentCodeNamespace.InternalCodeNamespaceList.Add(codeNamespace);
codeScopeList.Add(temp);//添加到域
//跳过域开始的符号位置
readIndex = startBigParanthesesStrIndex + 1;
//不是关键字,跳过
readIndex = skipStr(matchIndex, KeyWord_Class);
break;
case HandleCodeType.Class:
//检查是否真的是关键字
if (isKeyWord(matchIndex, code, KeyWord_Class))
matchIndex = skipStr(matchIndex, KeyWord_Class);
matchIndex++;//跳过一个空格
//找到域开始
int startBigParanthesesStrIndex = code.IndexOf(BeginBigParanthesesStr, matchIndex);
if (startBigParanthesesStrIndex < 0)
break;
//名字 如果有冒号,使用冒号前面部分
string[] nameStrs = code.Substring(matchIndex, startBigParanthesesStrIndex - matchIndex).Trim().Split(new string[] { Colon, NewlineChar_R, NewlineChar_N }, StringSplitOptions.RemoveEmptyEntries);
if (nameStrs.Length == 0)
break;
string name = nameStrs[0];
CodeClass codeClass = new CodeClass();
codeClass.Name = name;
CodeScope temp = new CodeScope();
temp.ScopeType = CodeScopeType.Class;
temp.Name = name;
temp.TargetObj = codeClass;
CodeScope parentCodeScope = getLastCodeScopeByNamespaceOrClass(codeScopeList);
if (parentCodeScope == null)
{//顶级类,创建一个空名字的命名空间
cs.CodeClassList.Add(codeClass);
else if (parentCodeScope.ScopeType == CodeScopeType.Namespace)
{//命名空间下的类
((CodeNamespace)parentCodeScope.TargetObj).CodeClassesList.Add(codeClass);
else if (parentCodeScope.ScopeType == CodeScopeType.Class)
{//内部类
((CodeClass)parentCodeScope.TargetObj).InternalCodeClassList.Add(codeClass);
codeScopeList.Add(temp);//添加到域
//跳过域开始的符号位置
readIndex = startBigParanthesesStrIndex + 1;
{//不是关键字,跳过
readIndex = skipStr(matchIndex, KeyWord_Class);
break;
case HandleCodeType.Comments:
matchIndex = skipStr(matchIndex, CommentsStr);
//因为 \r和\n都是换行符,所以都需要判断
//判断\r
int tempIndexR = indexOfStr(NewlineChar_R, false, matchIndex, code);
//判断\n
int tempIndexN = indexOfStr(NewlineChar_N, false, matchIndex, code);
if (tempIndexR >= 0
&& tempIndexN >= 0)
{//换行符都找到,比较两个换行符哪个在前
if (tempIndexR < tempIndexN)
readIndex = skipStr(tempIndexR, NewlineChar_R);
readIndex = skipStr(tempIndexN, NewlineChar_N);
else if (tempIndexR < 0
&& tempIndexN < 0)
{//换行符都没找到表示//后面没有换行符,后面的都是注释,解析结束
break;
else if (tempIndexR < 0)
readIndex = skipStr(tempIndexN, NewlineChar_N);
else if (tempIndexN < 0)
readIndex = skipStr(tempIndexR, NewlineChar_R);
break;
case HandleCodeType.BeginStarComments:
matchIndex = skipStr(matchIndex, BeginStarCommentsStr);
readIndex = skipEndMark(EndStarCommentsStr, false, matchIndex, code);
if (readIndex < 0)
{//未找到结尾,解析结束
break;
break;
case HandleCodeType.BeginBigParantheses:
matchIndex = skipStr(matchIndex, BeginBigParanthesesStr);
//找到当前域开始
CodeScope codeScope = new CodeScope();
codeScope.ScopeType = CodeScopeType.Other;
//记录该域
codeScopeList.Add(codeScope);
readIndex = matchIndex;
break;
case HandleCodeType.EndBigParantheses:
matchIndex = skipStr(matchIndex, EndBigParanthesesStr);
//去掉最近的域
int removeIndex = codeScopeList.Count - 1;
CodeScope codeScope = codeScopeList[removeIndex];
codeScopeList.RemoveAt(removeIndex);
readIndex = matchIndex;
break;
case HandleCodeType.DoubleQuotationMarks:
matchIndex = skipStr(matchIndex, DoubleQuotationMarksStr);
readIndex = skipEndMark(DoubleQuotationMarksStr, true, matchIndex, code);
if (readIndex < 0)
{//未找到结尾,解析结束
break;
break;
case HandleCodeType.Apostrophe:
matchIndex = skipStr(matchIndex, ApostropheStr);
readIndex = skipEndMark(ApostropheStr, true, matchIndex, code);
if (readIndex < 0)
{//未找到结尾,解析结束
break;
break;
default:
break;
return readIndex;
//处理代码类型
public enum HandleCodeType
/// <summary>
/// 命名空间
/// </summary>
Namespace,
/// <summary>
/// 类
/// </summary>
Class,
/// <summary>
/// 双斜杠注释
/// </summary>
Comments,
/// <summary>
/// 开始星星注释
/// </summary>
BeginStarComments,
/// <summary>
/// 开始大括号
/// </summary>
BeginBigParantheses,
/// <summary>
/// 结束大括号
/// </summary>
EndBigParantheses,
/// <summary>
/// 双引号
/// </summary>
DoubleQuotationMarks,
/// <summary>
/// 单引号
/// </summary>
Apostrophe,
#region Parse1
/// <summary>
/// 逐字匹配 太慢了
/// 读取类结构,只有命名空间和类名
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public static CodeStructure Parse1(string code)
CodeStructure cs = new CodeStructure();
if (string.IsNullOrEmpty(code))
return cs;
//记录作用域, 后进先出
List<CodeScope> codeScopeList = new List<CodeScope>();
int readIndex = 0;
char readChar;//当前下标字符
while (readIndex >= 0 &&
readIndex < code.Length)
readChar = code[readIndex];
#region 判断注释 //
if (isMatchStrIndex(CommentsStr, readIndex, code))
readIndex = handleCode(HandleCodeType.Comments, readIndex, code, cs, codeScopeList);
continue;
#endregion
#region 判断星号注释 /*
if (isMatchStrIndex(BeginStarCommentsStr, readIndex, code))
readIndex = handleCode(HandleCodeType.BeginStarComments, readIndex, code, cs, codeScopeList);
continue;
#endregion
#region 判断字符串 双引号
if (isMatchStrIndex(DoubleQuotationMarksStr, readIndex, code))
readIndex = handleCode(HandleCodeType.DoubleQuotationMarks, readIndex, code, cs, codeScopeList);
continue;
#endregion
#region 判断字符串 单引号
if (isMatchStrIndex(ApostropheStr, readIndex, code))
readIndex = handleCode(HandleCodeType.Apostrophe, readIndex, code, cs, codeScopeList);
continue;
#endregion
#region 判断左大括号 域开始{ ,但是这不是命名空间域和类域的开始,因为这两种域开始用关键字判断的
if (isMatchStrIndex(BeginBigParanthesesStr, readIndex, code))
readIndex = handleCode(HandleCodeType.BeginBigParantheses, readIndex, code, cs, codeScopeList);
continue;
#endregion
#region 判断右大括号 域结束 }
if (isMatchStrIndex(EndBigParanthesesStr, readIndex, code))
readIndex = handleCode(HandleCodeType.EndBigParantheses, readIndex, code, cs, codeScopeList);
continue;
#endregion
#region 判断命名空间名
if (isMatchStrIndex(KeyWord_Namespace, readIndex, code))
readIndex = handleCode(HandleCodeType.Namespace, readIndex, code, cs, codeScopeList);
continue;
#endregion
#region 判断类名
if (isMatchStrIndex(KeyWord_Class, readIndex, code))
readIndex = handleCode(HandleCodeType.Class, readIndex, code, cs, codeScopeList);
continue;
#endregion
readIndex++;
return cs;
#endregion
/// <summary>
/// 查找最后一个命名空间域或类域
/// </summary>
/// <param name="codeScopeStack"></param>
/// <returns></returns>
private static CodeScope getLastCodeScopeByNamespaceOrClass(List<CodeScope> codeScopeList)
//从后往前找
for (int i = codeScopeList.Count - 1; i >= 0; i--)
var codeScope = codeScopeList[i];
if (codeScope.ScopeType == CodeScopeType.Namespace ||
codeScope.ScopeType == CodeScopeType.Class)
return codeScope;
return null;
/// <summary>
/// 查找最后一个指定类型的域
/// </summary>
/// <param name="codeScopeStack"></param>
/// <returns></returns>
private static CodeScope getLastCodeScopeByCodeScopeType(List<CodeScope> codeScopeList, CodeScopeType codeScopeType)
//从后往前找
for (int i = codeScopeList.Count - 1; i >= 0; i--)
var codeScope = codeScopeList[i];
if (codeScope.ScopeType == codeScopeType)
return codeScope;
return null;
/// <summary>
/// 获得跳过匹配字符串下标
/// </summary>
/// <param name="readChar">当前下标值</param>
/// <param name="str"></param>
/// <param name="readIndex"></param>
/// <param name="code"></param>
/// <returns>-1 表示不匹配</returns>
private static bool isMatchStrIndex(string matchStr, int readIndex, string code)
char readChar = code[readIndex];
if (readChar.Equals(matchStr[0]))
int matchIndex = code.IndexOf(matchStr, readIndex);
if (matchIndex == readIndex)
{//匹配
return true;
return false;
/// <summary>
/// 获得跳过匹配字符的下标
/// </summary>
/// <param name="c"></param>
/// <param name="readIndex"></param>
/// <param name="code"></param>
/// <returns>-1 表示不匹配</returns>
private static bool isMatchCharIndex(char c, int readIndex, string code)
char readChar = code[readIndex];
return readChar.Equals(c);
/// <summary>
/// 跳过以指定结束符后
/// </summary>
/// <param name="endMark">结束符</param>
/// <param name="checkEscaping">是否检查转义</param>
/// <param name="startIndex">开始下标</param>
/// <param name="code">文字</param>
/// <returns>结束符后的下标,如果是-1,表示未找到结束符</returns>
private static int skipEndMark(string endMark, bool checkEscaping, int startIndex, string code)
startIndex = indexOfStr(endMark, checkEscaping, startIndex, code);
if (startIndex < 0)
return startIndex;
startIndex = skipStr(startIndex, endMark);
return startIndex;
private static int skipEndMark(char endMark, bool checkEscaping, int startIndex, string code)
return skipEndMark(endMark.ToString(), checkEscaping, startIndex, code);
//查找字符串
private static int indexOfStr(string str, bool checkEscaping, int startIndex, string code)
while (true)
startIndex = code.IndexOf(str, startIndex);
if (startIndex < 0)
{ //未找到双引号结尾,解析结束
break;
if (checkEscaping
&& code[startIndex - 1].Equals(RightSlashStr))
{//这个字符串的前一个字符是右斜杠,表示是转义字符串,需要继续查找下一个
startIndex = skipStr(startIndex, str);
continue;
{//找到这个结束符结束位置
break;
return startIndex;
/// <summary>
/// 跳过该字符串
/// </summary>
/// <param name="index"></param>
/// <param name="str"></param>
/// <returns></returns>
private static int skipStr(int index, string str)
return index += str.Length;
/// <summary>
/// 跳过该字符串
/// </summary>
/// <param name="index"></param>
/// <param name="str"></param>
/// <returns></returns>
private static int skipStr(int index, char c)
return index += 1;
//是否是关键字
private static bool isKeyWord(int matchIndex, string code, string keyWord)
//如果两侧字符,不是空格 制表符 换行符 就认为不是关键字
if (matchIndex > 0)
{//检查前面字符
char previousChar = code[matchIndex - 1];
if (!previousChar.Equals(cSpace)
&& !previousChar.Equals(cTabs)
&& !previousChar.Equals(cNewlineChar_R)
&& !previousChar.Equals(cNewlineChar_N))
return false;
if (matchIndex + keyWord.Length < code.Length - 1)
{//检查后面字符
char nextChar = code[matchIndex + keyWord.Length];
if (!nextChar.Equals(cSpace)
&& !nextChar.Equals(cTabs)
&& !nextChar.Equals(cNewlineChar_R)
&& !nextChar.Equals(cNewlineChar_N))
return false;
return true;
/// <summary>
/// 代码域类型
/// </summary>
public enum CodeScopeType
None = 0,
/// <summary>
/// 命名空间
/// </summary>
Namespace,
/// <summary>
/// 类
/// </summary>
Class,
/// <summary>
/// 其他类型域
/// </summary>
Other,
/// <summary>
/// 代码作用域
/// </summary>
public class CodeScope
/// <summary>
/// 名字
/// </summary>
public string Name;
/// <summary>
/// 作用域类型
/// </summary>
public CodeScopeType ScopeType;
/// <summary>