BITKit/Src/Core/Sharp/BITSharp.cs

348 lines
12 KiB
C#

using System;
using System.CodeDom.Compiler;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BITKit.Mod;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
#if UNITY_5_3_OR_NEWER
using System.Collections;
using UnityEngine;
#endif
namespace BITKit
{
public static class BITSharp
{
public static ICodeGenerator SharedCodeGenerator = new CodeGenerator();
public interface ICodeGenerator
{
public string Generate(Type type,string className=null);
IReadOnlyList<string> GenerateNamespaces(Type type);
string BeforeGenerate(Type type);
string AfterGenerate(Type type);
string GenerateMethod(MethodInfo methodInfo);
string GenerateMethodContext(MethodInfo methodInfo);
string GenerateField(FieldInfo fieldInfo);
string GenerateProperty(PropertyInfo propertyInfo);
string GenerateEvent(EventInfo eventInfo);
}
public class CodeGenerator:ICodeGenerator
{
public virtual string Generate(Type type, string className = null)
{
if (type.IsInterface is false) throw new InvalidDataException("The type have to be interface");
var codeBuilder = new StringBuilder();
var nameSpaces = new HashSet<string>();
foreach (var propertyInfo in type.GetProperties())
{
nameSpaces.Add(propertyInfo.PropertyType.Namespace);
}
foreach (var fieldInfo in type.GetFields())
{
nameSpaces.Add(fieldInfo.FieldType.Namespace);
}
foreach (var method in type.GetMethods())
{
if (method.DeclaringType is not null)
{
nameSpaces.Add(method.DeclaringType.Namespace);
}
nameSpaces.Add(method.ReturnType.Namespace);
foreach (var argument in method.GetGenericArguments())
{
nameSpaces.Add(argument.Namespace);
}
foreach (var parameterInfo in method.GetParameters())
{
nameSpaces.Add(parameterInfo.ParameterType.Namespace);
}
}
nameSpaces.Add("System");
nameSpaces.Add("System.Collections.Concurrent");
nameSpaces.Add("System.Collections.Generic");
nameSpaces.Add(type.Namespace);
foreach (var name in GenerateNamespaces(type))
{
nameSpaces.Add(name);
}
codeBuilder.AppendLine(string.Join('\n', nameSpaces.Where(x=>string.IsNullOrEmpty(x) is false).Select(x=>$"using {x};")));
//codeBuilder.AppendLine("namespace BITSharpGen");
//codeBuilder.AppendLine("{");
codeBuilder.AppendLine($"public class @{type.Name}Gen : {type.Name}");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(BeforeGenerate(type));
foreach (var fieldInfo in type.GetFields())
{
codeBuilder.AppendLine(GenerateField(fieldInfo));
}
foreach (var propertyInfo in type.GetProperties())
{
codeBuilder.AppendLine(GenerateProperty(propertyInfo));
}
// 遍历接口的所有成员,生成方法的默认实现
foreach (var method in type.GetMethods())
{
codeBuilder.AppendLine(GenerateMethod(method));
}
foreach (var eventInfo in type.GetEvents())
{
codeBuilder.AppendLine(GenerateEvent(eventInfo));
}
codeBuilder.AppendLine(AfterGenerate(type));
codeBuilder.AppendLine("}");
return codeBuilder.ToString();
}
public virtual IReadOnlyList<string> GenerateNamespaces(Type type)
{
return ArraySegment<string>.Empty;
}
public virtual string BeforeGenerate(Type type)
{
return string.Empty;
}
public virtual string AfterGenerate(Type type)
{
return string.Empty;
}
public virtual string GenerateMethod(MethodInfo methodInfo)
{
var codeBuilder = new StringBuilder();
HashSet<MethodInfo> ignoreMethods = new();
foreach (var eventInfo in methodInfo.DeclaringType!.GetEvents())
{
ignoreMethods.Add(eventInfo.AddMethod);
ignoreMethods.Add(eventInfo.RemoveMethod);
}
foreach (var propertyInfo in methodInfo.DeclaringType!.GetProperties())
{
if (propertyInfo.GetMethod is not null)
{
ignoreMethods.Add(propertyInfo.GetMethod);
}
if (propertyInfo.SetMethod is not null)
{
ignoreMethods.Add(propertyInfo.SetMethod);
}
}
if(ignoreMethods.Contains(methodInfo))return string.Empty;
string methodName = methodInfo.Name;
string parameters = string.Join(", ", methodInfo.GetParameters()
.Select(p => $"{(p.IsOut?"out":string.Empty)} {CSharpName(p.IsOut?p.ParameterType.GetElementType():p.ParameterType)} {p.Name}"));
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
// 检查是否为泛型方法
if (methodInfo.IsGenericMethod)
{
// 处理泛型方法
var genericArgs = string.Join(", ", methodInfo.GetGenericArguments()
.Select(CSharpName));
if (methodInfo.ReturnType == typeof(void))
{
codeBuilder.AppendLine($" public void {methodName}<{genericArgs}>({parameters}) {{ \n{GenerateMethodContext(methodInfo)}\n }}");
}
else
{
codeBuilder.AppendLine($" public {(isAwaitable?"async":string.Empty)} {CSharpName(methodInfo.ReturnType)} {methodName}<{genericArgs}>({parameters}) {{ {GenerateMethodContext(methodInfo)} }}");
}
}
else
{
if (methodInfo.ReturnType == typeof(void))
{
codeBuilder.AppendLine($" public void {methodName}({parameters}) {{ {GenerateMethodContext(methodInfo)}}}"); // Void 方法默认空实现
}
else
{
// 返回默认值
codeBuilder.AppendLine($" public {(isAwaitable?"async":string.Empty)} {CSharpName(methodInfo.ReturnType)} {methodName}({parameters}) {{ {GenerateMethodContext(methodInfo)} }}");
}
}
return codeBuilder.ToString();
}
public virtual string GenerateMethodContext(MethodInfo methodInfo)
{
var codeBuilder = new StringBuilder();
foreach (var parameterInfo in methodInfo.GetParameters())
{
if (parameterInfo.IsOut)
{
codeBuilder.AppendLine($"{parameterInfo.Name} = default;");
}
}
codeBuilder.AppendLine(methodInfo.ReturnType == typeof(void) ? string.Empty : $"return default;");
return codeBuilder.ToString();
}
public virtual string GenerateField(FieldInfo fieldInfo)
{
return $"public {CSharpName(fieldInfo.FieldType)} {fieldInfo.Name};";
}
public virtual string GenerateProperty(PropertyInfo propertyInfo)
{
return $"public {CSharpName(propertyInfo.PropertyType)} {propertyInfo.Name}" + "{ get;set; }";
}
public virtual string GenerateEvent(EventInfo eventInfo)
{
return $"public event {CSharpName(eventInfo.EventHandlerType)} {eventInfo.Name};";
}
}
#if UNITY_5_3_OR_NEWER
[RuntimeInitializeOnLoadMethod]
private static void Reload()
{
ReferencedAssemblies.Clear();
Dictionary.Clear();
assemblies = null;
}
#endif
public static readonly List<string> ReferencedAssemblies = new();
private static readonly ConcurrentDictionary<string, Type> Dictionary = new();
private static Assembly[] assemblies;
public static bool TryGetTypeFromFullName(string fullClassName, out Type type)
{
type = GetTypeFromFullName(fullClassName);
return type is not null ? true : false;
}
public static Type GetTypeFromFullName(string fullClassName)
{
return Dictionary.GetOrAdd(fullClassName, SearchType);
}
private static Type SearchType(string fullName)
{
if (string.IsNullOrWhiteSpace(fullName))
{
throw new ArgumentException("fullName is null or empty", nameof(fullName));
}
assemblies ??= AppDomain.CurrentDomain.GetAssemblies();
return assemblies.Select(assembly => assembly.GetType(fullName, false)).FirstOrDefault(type => type is not null);
}
public static IReadOnlyCollection<Assembly> GetReferencedAssemblies(Type type)
{
var result = new HashSet<Assembly>();
result.Add(type.Assembly);
foreach (var fieldInfo in type.GetFields())
{
result.Add(fieldInfo.FieldType.Assembly);
}
foreach (var propertyInfo in type.GetProperties())
{
result.Add(propertyInfo.PropertyType.Assembly);
}
foreach (var methodInfo in type.GetMethods())
{
result.Add(methodInfo.ReturnType.Assembly);
foreach (var argument in methodInfo.GetGenericArguments())
{
result.Add(argument.Assembly);
}
foreach (var parameterInfo in methodInfo.GetParameters())
{
result.Add(parameterInfo.ParameterType.Assembly);
}
}
return result;
}
public static string CSharpName(this Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType) return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments()
.Select(t => CSharpName(t))));
sb.Append(">");
return sb.ToString();
}
public static Assembly Compile(string code, ScriptOptions scriptOptions = null)
{
scriptOptions ??= ScriptOptions.Default;
var script = CSharpScript.Create(code, scriptOptions);
script.Compile();
using var ms = new MemoryStream();
var result = script.GetCompilation().Emit(ms);
if (!result.Success)
{
var report = new StringBuilder();
report.AppendLine(code);
foreach (var reference in script.GetCompilation().References)
{
report.AppendLine("Assembly referenced:" + reference.Display);
}
foreach (var diagnostic in result.Diagnostics)
{
report.AppendLine(diagnostic.ToString());
}
throw new Exception(report.ToString());
}
ms.Seek(0, SeekOrigin.Begin);
var assembly = Assembly.Load(ms.ToArray()); // 加载程序集
return assembly;
}
}
}