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 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(); 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 GenerateNamespaces(Type type) { return ArraySegment.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 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 ReferencedAssemblies = new(); private static readonly ConcurrentDictionary 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 GetReferencedAssemblies(Type type) { var result = new HashSet(); 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; } } }