承上篇的思路继续写,这次介绍字符串转 Type 的方式——类型分析。我的思路是把 Type 解析由“TestNet.Person, TestNet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null” 这种复杂的方式改为 “Person” 这种。这样,写起来简单明了,看起来也简单明了。
这次要说的东西比较简单,我借用一个例子说明:
首先,字符串“TestNet.Person”经过解析被分析为下面的 Token 集合:
- 标识、索引值 = 0、文本表示 = TestNet
- 句点、索引值 = 7、文本表示 = .
- 标识、索引值 = 8、文本表示 = Person
接着,判断标识符是否等于 int、bool 等 C# 基本类型文本名称,是则返回相应的类型( Type 实例);否则借用 Type.GetType 方法解析,进一步借用 Assembly.GetType 方法解析:
- 判断 TestNet 不是 int、bool 等 C# 基本类型的文本名称。
- 借用 Type.GetType 方法解析,返回值为 null 。
- 进一步借用 Assembly.GetType 方法解析,返回值为 null 。
- 读取下一个字符 ID = 句点,表示 TestNet 可能是命名空间名称,需要继续读取并分析。
- 判断 TestNet.Person 不是 int、bool 等 C# 基本类型的文本名称。
- 借用 Type.GetType 方法解析,返回值为 null 。
- 进一步借用 Assembly.GetType 方法解析,返回值为 TestNet.Person 类型的 Type 实例。
- 分析过程结束。
实际的过程中,我们还需要为分析方法添加额外的命名空间。这样,像“TestNet.Person” 这样的类型,就可以简写为 “Person”,符合我们使用 C# 的编码规范。
进一步考虑,我们可以从项目未引用的程序集中提取类型,这时候需要添加额外的 Assembly 。这样做的好处是:可以在不编译整个项目的情况下,更改某个配置,就可以使用新的程序集,新的逻辑。呵呵,是不是有点插件式编程的影子了。下一篇,应该会揭开面纱了。
下面,我给出类型解析类(TypeParser)的源码:
/// <summary>
/// 类型分析器
/// </summary>
[DebuggerStepThrough]
public class TypeParser
{
#region Properties
/// <summary>
/// 原始字符串分析结果
/// </summary>
private SymbolParseResult spResult = null;
/// <summary>
/// 获得待分析的类型可能用到的命名空间列表
/// </summary>
private IEnumerable<string> namespaces = Enumerable.Empty<string>();
/// <summary>
/// 获得额外的程序集信息列表
/// </summary>
private IEnumerable<Assembly> assemblyExtensions = Enumerable.Empty<Assembly>();
private TypeParser()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TypeParser"/> class.
/// </summary>
/// <param name="spResult">The symbol parse result.</param>
internal TypeParser(ref SymbolParseResult spResult)
{
this.spResult = spResult;
}
/// <summary>
/// 获得一个 <see cref="TypeParser"/> 类的实例对象
/// </summary>
public static TypeParser NewInstance
{
get { return new TypeParser(); }
}
private static Assembly[] _assemblies = null;
/// <summary>
/// 获取程序入口点所在目录程序集列表
/// </summary>
private static Assembly[] Assemblies
{
get
{
if (_assemblies == null)
{
var directory = string.Empty;
var assembly = Assembly.GetEntryAssembly();
if (assembly != null)
directory = Path.GetDirectoryName(assembly.Location);
else
directory = AppDomain.CurrentDomain.BaseDirectory;
var files = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly);
var data = new List<Assembly>(files.Length);
foreach (var item in files)
{
try
{
data.Add(Assembly.LoadFile(item));
}
catch { }
}
_assemblies = data.ToArray();
}
return _assemblies;
}
}
#endregion
#region Business Methods
/// <summary>
/// 添加可能遇到的命名空间字符串列表
/// </summary>
/// <param name="namespaces">新的命名空间字符串列表</param>
/// <returns>修改后的自身</returns>
public TypeParser SetNamespaces(IEnumerable<string> namespaces)
{
this.namespaces = namespaces ?? Enumerable.Empty<string>();
return this;
}
/// <summary>
/// 添加可能遇到的命名空间字符串列表
/// </summary>
/// <param name="namespaces">新的命名空间字符串列表</param>
/// <returns>修改后的自身</returns>
public TypeParser SetNamespaces(params string[] namespaces)
{
this.namespaces = namespaces ?? Enumerable.Empty<string>();
return this;
}
/// <summary>
/// 添加可能遇到的程序集信息列表
/// </summary>
/// <param name="assemblies">附加的程序集信息列表</param>
/// <returns>修改后的自身</returns>
public TypeParser SetAssemblies(IEnumerable<Assembly> assemblies)
{
assemblyExtensions = assemblies ?? Enumerable.Empty<Assembly>();
return this;
}
/// <summary>
/// 添加可能遇到的程序集信息列表
/// </summary>
/// <param name="assemblies">附加的程序集信息列表</param>
/// <returns>修改后的自身</returns>
public TypeParser SetAssemblies(params Assembly[] assemblies)
{
assemblyExtensions = assemblies ?? Enumerable.Empty<Assembly>();
return this;
}
/// <summary>
/// 解析字符串为类型
/// </summary>
/// <returns>读取的类型</returns>
public Type Resolve(string typeString)
{
spResult = SymbolParser.Build(typeString);
return ReadType();
}
#endregion
#region Private Methods
internal Type ReadType(string typeName = null, bool ignoreException = false)
{
Type type = null;
StringBuilder sbValue =
new StringBuilder(string.IsNullOrEmpty(typeName) ? spResult.Next() : typeName);
do
{
// read generic parameters
if (spResult.PeekNext() == "<")
{
spResult.Skip();
List<Type> listGenericType = new List<Type>();
while (true)
{
listGenericType.Add(ReadType());
if (spResult.PeekNext() == ",")
spResult.Skip();
else
break;
}
NextIsEqual(">");
sbValue.AppendFormat("`{0}[{1}]", listGenericType.Count,
string.Join(",", listGenericType
.Select(p => "[" + p.AssemblyQualifiedName + "]").ToArray()));
}
type = GetType(sbValue.ToString());
if (type == null)
{
bool result = NextIsEqual(".", false);
if (!result)
{
if (ignoreException)
break;
throw new ParseUnfindTypeException(sbValue.ToString(), spResult.Index);
}
sbValue.Append(".");
sbValue.Append(spResult.Next());
}
} while (type == null);
return type;
}
internal Type GetType(string typeName)
{
if (string.IsNullOrEmpty(typeName))
return null;
// Nullable
bool isNullable = false;
if (typeName.EndsWith("?"))
{
isNullable = true;
typeName = typeName.Substring(0, typeName.Length - 1);
}
Type type;
switch (typeName)
{
case "bool":
type = typeof(bool);
break;
case "byte":
type = typeof(byte);
break;
case "sbyte":
type = typeof(sbyte);
break;
case "char":
type = typeof(char);
break;
case "decimal":
type = typeof(decimal);
break;
case "double":
type = typeof(double);
break;
case "float":
type = typeof(float);
break;
case "int":
type = typeof(int);
break;
case "uint":
type = typeof(uint);
break;
case "long":
type = typeof(long);
break;
case "ulong":
type = typeof(ulong);
break;
case "object":
type = typeof(object);
break;
case "short":
type = typeof(short);
break;
case "ushort":
type = typeof(ushort);
break;
case "string":
type = typeof(string);
break;
default:
{
// Suppose typeName is full name of class
type = GetTypeCore(typeName);
// Did not find the namespace to use all of the match again and again
if (type == null)
{
foreach (string theNamespace in namespaces)
{
type = GetTypeCore(string.Concat(theNamespace, ".", typeName));
// To find a qualified first class
if (type != null)
break;
}
}
}
break;
}
if (isNullable && type != null)
type = typeof(Nullable<>).MakeGenericType(type);
return type;
}
private Type GetTypeCore(string typeName)
{
Type type = Type.GetType(typeName);
if (type != null)
return type;
Assembly[] listAssembly = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in listAssembly)
{
type = assembly.GetType(typeName, false, false);
if (type != null)
return type;
}
if (assemblyExtensions != null && assemblyExtensions.Any())
{
foreach (Assembly assembly in assemblyExtensions)
{
type = assembly.GetType(typeName, false, false);
if (type != null)
return type;
}
}
if (Assemblies != null && Assemblies.Any())
{
foreach (Assembly assembly in Assemblies)
{
type = assembly.GetType(typeName, false, false);
if (type != null)
return type;
}
}
return null;
}
private bool NextIsEqual(string symbol, bool throwExceptionIfError = true)
{
if (spResult.Next() != symbol)
{
if (throwExceptionIfError)
throw new ApplicationException(string.Format("{0} isn't the next token", symbol));
else
return false;
}
return true;
}
#endregion
}
使用到的一个异常类如下:
/// <summary>
/// Parse UnfindType Exception
/// </summary>
[Serializable]
[DebuggerStepThrough]
public class ParseUnfindTypeException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ParseUnfindTypeException"/> class.
/// </summary>
/// <param name="typeName">Name of the type.</param>
/// <param name="errorIndex">Index of the error.</param>
public ParseUnfindTypeException(string typeName, int errorIndex)
: base(string.Format("{0} in the vicinity of the type \"{1}\" not found", errorIndex, typeName))
{
}
}
需要注意的是,这个类用到了上次提到的字符串解析结果,需要两者相互配合。嘿嘿,再给个示意图诱惑一下:
如果您感觉有用,顺手点下推荐,谢了!