基于.net standard 的动态编译实现代码

2025-05-29 0 65

在上篇文章[基于.net core 微服务的另类实现]结尾处,提到了如何方便自动的生成微服务的客户端代理,使对于调用方透明,同时将枯燥的东西使用框架集成,以提高使用便捷性。在尝试了基于 Emit 中间语言后,最终决定使用生成代码片段然后动态编译的模式实现。

1.背景:

其一在前文中,我们通过框架实现了微服务面向使用者的透明调用,但是需要为每个服务写一个客户端代理,显得异常繁琐,其二项目中前端站点使用了传统的.Net Framework 框架,后端微服务我们使用了.Net Core 框架改造,短时间将前端站点调整成 .Net Core 框架亦不现实,为了能同时支持这两种框架。如何 .Net Standard 框架来自动创建微服务的客户端代理成为我们必须解决的问题。

2.问题转化

我们在回头简单看一下我们现在期望的微服务客户端代理长的样子:

基于.net standard 的动态编译实现代码

通过上面分析,我们只需要将服务接口中的每个方法,判断是否有返回值,如果有返回值调用Invoke<ReturnType>方法,没有返回值调用InvokeWithoutReturn方法,然后依次将接口名,方法名以及方法的参数按顺序传入即可。各位如果是熟悉Java的同学,这个问题很容易解决,使用动态代理创建一个这样的匿名类即可,但在.net 的世界里,动态代理的实现确显得异常麻烦。
首先想到是通过中间语言 IL 的 Emit 实现,但无奈这个使用起来实在是太不友好了, 几经折腾最终还是选择放弃了,后又想到其实可以通过动态生成这个代码片段,动态编译后加载到系统程序集中,应该就可以了。于是在这个方向的指引下,我们尝试着去一步步实现这个问题。

3.解决方案

如何生成这个代码片段? 通过上面的分析,我们知道只需要将接口反射获取其中的公共方法,并将接口的每个方法签名原样复制,在根据接口方法是否有返回值分别调用RemoteServiceProxy基类中相关方法即可,不过需要特殊注意的泛型方法翻译,以下是生成这个代码片段的参考实现.

寻找出为服务接口程序集文件,并处理每个文件

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25
private static StringBuilder CreateApiProxyCode()

{

var path = GetBinPath();

var dir = new DirectoryInfo(path);

//获取项目中微服务接口文件

var files = dir.GetFiles("XZL*.Api.dll");

var codeStringBuilder = new StringBuilder(1024);

//添加必要的using

codeStringBuilder

.AppendLine("using System;")

.AppendLine("using System.Collections.Generic;")

.AppendLine("using System.Text;")

.AppendLine("using XZL.Infrastructure.ApiService;")

.AppendLine("using XZL.Infrastructure.Defines;")

.AppendLine("using XZL.Model;")

.AppendLine("namespace XZL.ApiClientProxy")

.AppendLine("{"); //namespace begin

//处理每个文件中的接口信息

foreach (var file in files)

{

CreateApiProxyCodeFromFile(codeStringBuilder, file);

}

codeStringBuilder.AppendLine("}"); //namespace end

return codeStringBuilder;

}

处理每个文件中的接口类型,并将每个程序集的依赖程序集找出来,方便后面动态编译

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49
private static void CreateApiProxyCodeFromFile(StringBuilder fileCodeBuilder, FileInfo file)

{

try

{

Assembly apiAssembly = Assembly.Load(file.Name.Substring(0, file.Name.Length - 4));

var types = apiAssembly

.GetTypes()

.Where(c => c.IsInterface && c.IsPublic)

.ToList();

var apiSvcType = typeof(IApiService);

bool isNeed = false;

foreach (Type type in types)

{

//找出期望的接口类型

if (!apiSvcType.IsAssignableFrom(type))

{

continue;

}

//找出接口的所有方法

var methods = type.GetMethods(BindingFlags.Public

| BindingFlags.FlattenHierarchy

| BindingFlags.Instance);

if (!methods.Any())

{

continue;

}

//定义代理类名,以及实现接口和继承RemoteServiceProxy

fileCodeBuilder.AppendLine($"public class {type.FullName.Replace(".", "_")}Proxy :" +

$"RemoteServiceProxy, {type.FullName}")

.AppendLine("{"); //class begin

//处理每个方法

foreach (var mth in methods)

{

CreateApiProxyCodeFromMethod(fileCodeBuilder, type, mth);

}

fileCodeBuilder.AppendLine("}"); //class end

isNeed = true;

}

if (isNeed)

{

var apiRefAsms = apiAssembly.GetReferencedAssemblies();

refAssemblyList.Add(apiAssembly.GetName());

refAssemblyList.AddRange(apiRefAsms);

}

}

catch

{

}

}

处理接口中的每个方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54
private static void CreateApiProxyCodeFromMethod(

StringBuilder fileCodeBuilder,

Type type,

MethodInfo mth)

{

var isMthReturn = !mth.ReturnType.Equals(typeof(void));

fileCodeBuilder.Append("public ");

//添加返回值

if (isMthReturn)

{

fileCodeBuilder.Append(GetFriendlyTypeName(mth.ReturnType)).Append(" ");

}

else

{

fileCodeBuilder.Append(" void ");

}

//方法参数开始

fileCodeBuilder.Append(mth.Name).Append("(");

var mthParams = mth.GetParameters();

if (mthParams.Any())

{

var mthparaList = new List<string>();

foreach (var p in mthParams)

{

mthparaList.Add(GetFriendlyTypeName(p.ParameterType) + " " + p.Name);

}

fileCodeBuilder.Append(string.Join(",", mthparaList));

}

//方法参数结束

fileCodeBuilder.Append(")");

//方法体开始

fileCodeBuilder.AppendLine("{");

if (isMthReturn)

{

//返回值

fileCodeBuilder.Append("return Invoke<")

.Append(GetFriendlyTypeName(mth.ReturnType))

.Append(">");

}

else

{

fileCodeBuilder.Append(" InvokeWithoutReturn");

}

//拼接接口名及方法名

fileCodeBuilder.Append($"(\\"{type.FullName}\\",\\"{mth.Name}\\"");

//方法本身参数

if (mthParams.Any())

{

fileCodeBuilder.Append(",").Append(string.Join(",", mthParams.Select(t => t.Name)));

}

fileCodeBuilder.Append(");");

//方法体结束

fileCodeBuilder.AppendLine("}");

}

获取泛型类型字符串

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22
private static string GetFriendlyTypeName(Type type)

{

if (!type.IsGenericType)

{

return type.FullName;

}

string friendlyName = type.Name;

int iBacktick = friendlyName.IndexOf('`');

if (iBacktick > 0)

{

friendlyName = friendlyName.Remove(iBacktick);

}

friendlyName += "<";

Type[] typeParameters = type.GetGenericArguments();

for (int i = 0; i < typeParameters.Length; ++i)

{

string typeParamName = GetFriendlyTypeName(typeParameters[i]);

friendlyName += (i == 0 ? typeParamName : "," + typeParamName);

}

friendlyName += ">";

return friendlyName;

}

如何添加依赖

既然是要编译源码,那么源码中的依赖必不可少,在上一步中我们已经将每个程序集的依赖一并找出,接下来我们将这些依赖全部整理出来

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29
//缓存程序集依赖

var references = new List<MetadataReference>();

var refAsmFiles = new List<string>();

//系统依赖

var sysRefLocation = typeof(Enumerable).GetTypeInfo().Assembly.Location;

refAsmFiles.Add(sysRefLocation);

//refAsmFiles原本缓存的程序集依赖

refAsmFiles.Add(typeof(object).GetTypeInfo().Assembly.Location);

refAsmFiles.AddRange(refAssemblyList.Select(t => Assembly.Load(t).Location).Distinct().ToList());

//传统.NetFramework 需要添加mscorlib.dll

var coreDir = Directory.GetParent(sysRefLocation);

var mscorlibFile = coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll";

if (File.Exists(mscorlibFile))

{

references.Add(MetadataReference.CreateFromFile(mscorlibFile));

}

var apiAsms = refAsmFiles.Select(t => MetadataReference.CreateFromFile(t)).ToList();

references.AddRange(apiAsms);

//当前程序集依赖

var thisAssembly = Assembly.GetEntryAssembly();

if (thisAssembly != null)

{

var referencedAssemblies = thisAssembly.GetReferencedAssemblies();

foreach (var referencedAssembly in referencedAssemblies)

{

var loadedAssembly = Assembly.Load(referencedAssembly);

references.Add(MetadataReference.CreateFromFile(loadedAssembly.Location));

}

}

编译

有了代码片段, 也有了编译程序集依赖, 接下来就是最重要的编译了.

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30
//定义编译后文件名

var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Proxy");

if (!Directory.Exists(path))

{

Directory.CreateDirectory(path);

}

var apiRemoteProxyDllFile = Path.Combine(path,

apiRemoteAsmName + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".dll");

var tree = SyntaxFactory.ParseSyntaxTree(codeBuilder.ToString());

var compilation = CSharpCompilation.Create(apiRemoteAsmName)

.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))

.AddReferences(references)

.AddSyntaxTrees(tree);

//执行编译

EmitResult compilationResult = compilation.Emit(apiRemoteProxyDllFile);

if (compilationResult.Success)

{

// Load the assembly

apiRemoteAsm = Assembly.LoadFrom(apiRemoteProxyDllFile);

}

else

{

foreach (Diagnostic codeIssue in compilationResult.Diagnostics)

{

string issue = $"ID: {codeIssue.Id}, Message: {codeIssue.GetMessage()}," +

$" Location: { codeIssue.Location.GetLineSpan()}, " +

$"Severity: { codeIssue.Severity}";

AppRuntimes.Instance.Loger.Error("自动编译代码出现异常," + issue);

}

}

结语

在经过以上处理后,虽算不上完美,但顺利的实现了我们期望的样子,在之前的GetService中,当发现属于远程服务的时候,只需要类似如下形式返回代理对象即可。同时为增加调用更加顺畅,我们将此编译的时机定在了发生在程序启动的时候,ps 当然或许还有一些其他更合适的时机.

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17
static ConcurrentDictionary<string, Object> svcInstance = new ConcurrentDictionary<string, object>();

var typeName = "XZL.ApiClientProxy." + typeof(TService).FullName.Replace(".", "_") + "Proxy";

object obj = null;

if (svcInstance.TryGetValue(typeName, out obj) && obj != null)

{

return (TService)obj;

}

try

{

obj = (TService)apiRemoteAsm.CreateInstance(typeName);

svcInstance.TryAdd(typeName, obj);

}

catch

{

throw new ICVIPException($"未找到 {typeof(TService).FullName} 的有效代理");

}

return (TService)obj;

总结

以上所述是小编给大家介绍的基于.net standard动态编译实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对快网idc网站的支持!

原文链接:https://www.cnblogs.com/xie-zhonglai/p/dynamic_compilation_netstandard.html

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 基于.net standard 的动态编译实现代码 https://www.kuaiidc.com/97991.html

相关文章

发表评论
暂无评论