123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Linq;
- using System.Text;
- using ILRuntime.Runtime.Enviorment;
- using ILRuntime.CLR.Utils;
- namespace ILRuntime.Runtime.CLRBinding
- {
- static class MethodBindingGenerator
- {
- static string GenerateParameterTypes(MethodInfo i, bool needBracket)
- {
- string clsName, realClsName;
- bool isByRef;
- var param = i.GetParameters();
- StringBuilder sb2 = new StringBuilder();
- if (needBracket)
- sb2.Append("{");
- bool first = true;
- foreach (var j in param)
- {
- if (first)
- first = false;
- else
- sb2.Append(", ");
- sb2.Append("typeof(");
- j.ParameterType.GetClassName(out clsName, out realClsName, out isByRef);
- sb2.Append(realClsName);
- sb2.Append(")");
- if (isByRef)
- sb2.Append(".MakeByRefType()");
- }
- if (needBracket)
- sb2.Append("}");
- return sb2.ToString();
- }
- internal static string GenerateMethodRegisterCode(this Type type, MethodInfo[] methods, HashSet<MethodBase> excludes, out bool needMethods)
- {
- needMethods = false;
- MethodInfo[] allMethods = type.GetMethods();
- StringBuilder sb = new StringBuilder();
- int idx = 0;
- bool isMethodsGot = false;
- foreach (var i in methods)
- {
- if (excludes != null && excludes.Contains(i))
- continue;
- if (type.ShouldSkipMethod(i))
- continue;
- if (i.IsGenericMethod)
- {
- if (!isMethodsGot)
- {
- sb.AppendLine(@" Dictionary<string, List<MethodInfo>> genericMethods = new Dictionary<string, List<MethodInfo>>();
- List<MethodInfo> lst = null;
- foreach(var m in type.GetMethods())
- {
- if(m.IsGenericMethodDefinition)
- {
- if (!genericMethods.TryGetValue(m.Name, out lst))
- {
- lst = new List<MethodInfo>();
- genericMethods[m.Name] = lst;
- }
- lst.Add(m);
- }
- }");
- isMethodsGot = true;
- }
- var param = i.GetGenericArguments();
- StringBuilder sb2 = new StringBuilder();
- sb2.Append("{");
- bool first = true;
- string clsName, realClsName;
- bool isByRef;
- foreach (var j in param)
- {
- if (first)
- first = false;
- else
- sb2.Append(", ");
- sb2.Append("typeof(");
- j.GetClassName(out clsName, out realClsName, out isByRef);
- sb2.Append(realClsName);
- sb2.Append(")");
- if (isByRef)
- sb2.Append(".MakeByRefType()");
- }
- sb2.Append("}");
- sb.AppendLine(string.Format(" args = new Type[]{0};", sb2));
- sb.AppendLine(string.Format(" if (genericMethods.TryGetValue(\"{0}\", out lst))", i.Name));
- sb.Append(@" {
- foreach(var m in lst)
- {
- if(m.MatchGenericParameters(args, ");
- if (i.ReturnType != typeof(void))
- {
- sb.Append("typeof(");
- i.ReturnType.GetClassName(out clsName, out realClsName, out isByRef);
- sb.Append(realClsName);
- sb.Append(")");
- }
- else
- sb.Append("typeof(void)");
- if (i.GetParameters().Length > 0)
- {
- sb.Append(", ");
- sb.Append(GenerateParameterTypes(i, false));
- }
- sb.Append(@"))
- {
- method = m.MakeGenericMethod(args);
- app.RegisterCLRMethodRedirection(method, ");
- sb.AppendLine(string.Format("{0}_{1});", i.Name, idx));
- sb.AppendLine(@"
- break;
- }
- }
- }");
- }
- else
- {
- string clsName, realClsName;
- bool isByRef;
- var param = i.GetParameters();
- string sb2 = GenerateParameterTypes(i, true);
- sb.AppendLine(string.Format(" args = new Type[]{0};", sb2));
- i.ReturnType.GetClassName(out clsName, out realClsName, out isByRef);
- if ((i.Name.Equals("op_Implicit") || i.Name.Equals("op_Explicit")) && allMethods.Count(m => m.Name.Equals(i.Name)) > 1)
- {
- // Type conversions can have different return types
- needMethods = true;
- sb.AppendLine(string.Format(" method = methods.Where(t => t.Name.Equals(\"{0}\") && t.ReturnType == typeof({1}) && t.CheckMethodParams(args)).Single();", i.Name, realClsName));
- }
- else if (allMethods.Any(m => m.IsGenericMethod && m.Name.Equals(i.Name) && m.CheckMethodParams(param)))
- {
- // Check for a generic method with the same name and params
- needMethods = true;
- sb.AppendLine(string.Format(" method = methods.Where(t => t.Name.Equals(\"{0}\") && t.CheckMethodParams(args)).Single();", i.Name));
- } else
- sb.AppendLine(string.Format(" method = type.GetMethod(\"{0}\", flag, null, args, null);", i.Name));
- sb.AppendLine(string.Format(" app.RegisterCLRMethodRedirection(method, {0}_{1});", i.Name, idx));
- }
- idx++;
- }
- return sb.ToString();
- }
-
- internal static string GenerateMethodWraperCode(this Type type, MethodInfo[] methods, string typeClsName, HashSet<MethodBase> excludes, List<Type> valueTypeBinders, Enviorment.AppDomain domain)
- {
- StringBuilder sb = new StringBuilder();
- bool isMultiArr = type.IsArray && type.GetArrayRank() > 1;
- int idx = 0;
- foreach (var i in methods)
- {
- if (excludes != null && excludes.Contains(i))
- continue;
- if (type.ShouldSkipMethod(i))
- continue;
- bool isProperty = i.IsSpecialName;
- var param = i.GetParameters();
- int paramCnt = param.Length;
- if (!i.IsStatic)
- paramCnt++;
- sb.AppendLine(string.Format(" static StackObject* {0}_{1}(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)", i.Name, idx));
- sb.AppendLine(" {");
- sb.AppendLine(" ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;");
- if (param.Length != 0 || !i.IsStatic)
- sb.AppendLine(" StackObject* ptr_of_this_method;");
- sb.AppendLine(string.Format(" StackObject* __ret = ILIntepreter.Minus(__esp, {0});", paramCnt));
- sb.AppendLine();
- bool hasByRef = param.HasByRefParam();
- string shouldFreeParam = hasByRef ? "false" : "true";
- for (int j = param.Length; j > 0; j--)
- {
- var p = param[j - 1];
- sb.AppendLine(string.Format(" ptr_of_this_method = ILIntepreter.Minus(__esp, {0});", param.Length - j + 1));
- p.ParameterType.AppendArgumentCode(sb, j, p.Name, valueTypeBinders, isMultiArr, hasByRef, true);
- sb.AppendLine();
- }
- bool noUnbox = (type.Name.Contains("AsyncTaskMethodBuilder") || type.FullName.StartsWith("System.Runtime.CompilerServices.AsyncVoidMethodBuilder")) && i.Name == "Start";
- if (!i.IsStatic)
- {
- sb.AppendLine(string.Format(" ptr_of_this_method = ILIntepreter.Minus(__esp, {0});", paramCnt));
- if (type.IsPrimitive)
- sb.AppendLine(string.Format(" {0} instance_of_this_method = GetInstance(__domain, ptr_of_this_method, __mStack);", typeClsName));
- else if (type.IsValueType && !type.IsPrimitive && valueTypeBinders != null && valueTypeBinders.Contains(type))
- {
- string clsName, realClsName;
- bool isByRef;
- type.GetClassName(out clsName, out realClsName, out isByRef);
- sb.AppendLine(string.Format(" {0} instance_of_this_method = new {0}();", realClsName));
- sb.AppendLine(string.Format(" if (ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder != null) {{", clsName));
- sb.AppendLine(string.Format(" ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder.ParseValue(ref instance_of_this_method, __intp, ptr_of_this_method, __mStack, false);", clsName));
- sb.AppendLine(" } else {");
- if (type.IsValueType)
- sb.AppendLine(" ptr_of_this_method = ILIntepreter.GetObjectAndResolveReference(ptr_of_this_method);");
- sb.AppendLine(string.Format(" instance_of_this_method = {0};", type.GetRetrieveValueCode(typeClsName)));
- if (!type.IsValueType && !hasByRef)
- sb.AppendLine(" __intp.Free(ptr_of_this_method);");
- sb.AppendLine(" }");
- }
- else
- {
- if (type.IsValueType && !type.IsPrimitive)
- {
- sb.AppendLine(" ptr_of_this_method = ILIntepreter.GetObjectAndResolveReference(ptr_of_this_method);");
- }
- if (noUnbox)
- sb.AppendLine(string.Format(" object instance_of_this_method = {0};", type.GetRetrieveValueCode(typeClsName)));
- else
- sb.AppendLine(string.Format(" {0} instance_of_this_method = {1};", typeClsName, type.GetRetrieveValueCode(typeClsName)));
- if (!type.IsValueType && !hasByRef)
- sb.AppendLine(" __intp.Free(ptr_of_this_method);");
- }
- }
- sb.AppendLine();
- if (i.ReturnType != typeof(void))
- {
- sb.Append(" var result_of_this_method = ");
- }
- else
- sb.Append(" ");
- string genericArguments = "";
- if (i.IsGenericMethod)
- {
- var p = i.GetGenericArguments();
- StringBuilder sb2 = new StringBuilder();
- bool first = true;
- sb2.Append('<');
- foreach (var j in p)
- {
- if (first)
- first = false;
- else
- sb2.Append(", ");
- string clsName, realClsName;
- bool isByRef;
- j.GetClassName(out clsName, out realClsName, out isByRef);
- sb2.Append(realClsName);
- }
- sb2.Append('>');
- genericArguments = sb2.ToString();
- }
-
- if (i.IsStatic)
- {
- if (isProperty)
- {
- string[] t = new string[2];
- int firstUnderlineIndex = i.Name.IndexOf("_");
- t[0] = i.Name.Substring(0, firstUnderlineIndex);
- t[1] = i.Name.Substring(firstUnderlineIndex + 1);
- string propType = t[0];
- if (propType == "get")
- {
- bool isIndexer = param.Length > 0;
- if (isIndexer)
- {
- sb.AppendLine(string.Format("{1}[{0}];", param[0].Name, typeClsName));
- }
- else
- sb.AppendLine(string.Format("{1}.{0};", t[1], typeClsName));
- }
- else if (propType == "set")
- {
- bool isIndexer = param.Length > 1;
- if (isIndexer)
- {
- sb.AppendLine(string.Format("{2}[{0}] = {1};", param[0].Name, param[1].Name, typeClsName));
- }
- else
- sb.AppendLine(string.Format("{2}.{0} = {1};", t[1], param[0].Name, typeClsName));
- }
- else if (propType == "op")
- {
- switch (t[1])
- {
- case "Equality":
- sb.AppendLine(string.Format("{0} == {1};", param[0].Name, param[1].Name));
- break;
- case "Inequality":
- sb.AppendLine(string.Format("{0} != {1};", param[0].Name, param[1].Name));
- break;
- case "Addition":
- sb.AppendLine(string.Format("{0} + {1};", param[0].Name, param[1].Name));
- break;
- case "Subtraction":
- sb.AppendLine(string.Format("{0} - {1};", param[0].Name, param[1].Name));
- break;
- case "Multiply":
- sb.AppendLine(string.Format("{0} * {1};", param[0].Name, param[1].Name));
- break;
- case "Division":
- sb.AppendLine(string.Format("{0} / {1};", param[0].Name, param[1].Name));
- break;
- case "GreaterThan":
- sb.AppendLine(string.Format("{0} > {1};", param[0].Name, param[1].Name));
- break;
- case "GreaterThanOrEqual":
- sb.AppendLine(string.Format("{0} >= {1};", param[0].Name, param[1].Name));
- break;
- case "LessThan":
- sb.AppendLine(string.Format("{0} < {1};", param[0].Name, param[1].Name));
- break;
- case "LessThanOrEqual":
- sb.AppendLine(string.Format("{0} <= {1};", param[0].Name, param[1].Name));
- break;
- case "UnaryNegation":
- sb.AppendLine(string.Format("-{0};", param[0].Name));
- break;
- case "LogicalNot":
- sb.AppendLine(string.Format("!{0};", param[0].Name));
- break;
- case "Modulus":
- sb.AppendLine(string.Format("{0} % {1};", param[0].Name, param[1].Name));
- break;
- case "Implicit":
- case "Explicit":
- {
- string clsName, realClsName;
- bool isByRef;
- i.ReturnType.GetClassName(out clsName, out realClsName, out isByRef);
- sb.AppendLine(string.Format("({1}){0};", param[0].Name, realClsName));
- }
- break;
- case "Increment":
- sb.AppendLine(string.Format("++{0};", param[0].Name));
- break;
- case "Decrement":
- sb.AppendLine(string.Format("--{0};", param[0].Name));
- break;
- default:
- throw new NotImplementedException(i.Name);
- }
- }
- else if(propType == "add")
- {
- string clsName, realClsName;
- bool isByRef;
- i.DeclaringType.GetClassName(out clsName, out realClsName, out isByRef);
- sb.AppendLine(string.Format("{0}.{1} += {2};", realClsName, i.Name.Substring(4), param[0].Name));
- }
- else if (propType == "remove")
- {
- string clsName, realClsName;
- bool isByRef;
- i.DeclaringType.GetClassName(out clsName, out realClsName, out isByRef);
- sb.AppendLine(string.Format("{0}.{1} -= {2};", realClsName, i.Name.Substring(7), param[0].Name));
- }
- else
- throw new NotImplementedException();
- }
- else
- {
- sb.Append(string.Format("{0}.{1}{2}(", typeClsName, i.Name, genericArguments));
- param.AppendParameters(sb);
- sb.AppendLine(");");
- }
- }
- else
- {
- if (isProperty)
- {
- string[] t = new string[2];
- int firstUnderlineIndex = i.Name.IndexOf("_");
- t[0] = i.Name.Substring(0, firstUnderlineIndex);
- t[1] = i.Name.Substring(firstUnderlineIndex + 1);
- string propType = t[0];
- if(noUnbox)
- {
- if (propType == "get")
- {
- bool isIndexer = param.Length > 0;
- if (isIndexer)
- {
- sb.AppendLine(string.Format("(({1})instance_of_this_method)[{0}];", param[0].Name, typeClsName));
- }
- else
- sb.AppendLine(string.Format("(({1})instance_of_this_method).{0};", t[1], typeClsName));
- }
- else if (propType == "set")
- {
- bool isIndexer = param.Length > 1;
- if (isIndexer)
- {
- sb.AppendLine(string.Format("(({2})instance_of_this_method)[{0}] = {1};", param[0].Name, param[1].Name, typeClsName));
- }
- else
- sb.AppendLine(string.Format("(({2})instance_of_this_method).{0} = {1};", t[1], param[0].Name, typeClsName, typeClsName));
- }
- else if (propType == "add")
- {
- sb.AppendLine(string.Format("(({2})instance_of_this_method).{0} += {1};", i.Name.Substring(4), param[0].Name, typeClsName));
- }
- else if (propType == "remove")
- {
- sb.AppendLine(string.Format("(({2})instance_of_this_method).{0} -= {1};", i.Name.Substring(7), param[0].Name, typeClsName));
- }
- else
- throw new NotImplementedException();
- }
- else
- {
- if (propType == "get")
- {
- bool isIndexer = param.Length > 0;
- if (isIndexer)
- {
- sb.AppendLine(string.Format("instance_of_this_method[{0}];", param[0].Name));
- }
- else
- sb.AppendLine(string.Format("instance_of_this_method.{0};", t[1]));
- }
- else if (propType == "set")
- {
- bool isIndexer = param.Length > 1;
- if (isIndexer)
- {
- sb.AppendLine(string.Format("instance_of_this_method[{0}] = {1};", param[0].Name, param[1].Name));
- }
- else
- sb.AppendLine(string.Format("instance_of_this_method.{0} = {1};", t[1], param[0].Name));
- }
- else if (propType == "add")
- {
- sb.AppendLine(string.Format("instance_of_this_method.{0} += {1};", i.Name.Substring(4), param[0].Name));
- }
- else if (propType == "remove")
- {
- sb.AppendLine(string.Format("instance_of_this_method.{0} -= {1};", i.Name.Substring(7), param[0].Name));
- }
- else
- throw new NotImplementedException();
- }
- }
- else if (isMultiArr)
- {
- if (i.Name == "Get")
- {
- sb.Append("instance_of_this_method[");
- param.AppendParameters(sb, true);
- sb.AppendLine("];");
- }
- else
- {
- sb.Append("instance_of_this_method[");
- param.AppendParameters(sb, true, 1);
- sb.Append("]");
- sb.Append(" = a");
- sb.Append(param.Length);
- sb.AppendLine(";");
- }
- }
- else
- {
- if(noUnbox)
- sb.Append(string.Format("(({2})instance_of_this_method).{0}{1}(", i.Name, genericArguments, typeClsName));
- else
- sb.Append(string.Format("instance_of_this_method.{0}{1}(", i.Name, genericArguments));
- param.AppendParameters(sb);
- sb.AppendLine(");");
- }
- }
- sb.AppendLine();
- //Ref/Out
- for (int j = param.Length; j > 0; j--)
- {
- var p = param[j - 1];
- if (!p.ParameterType.IsByRef && !hasByRef)
- {
- continue;
- }
- string clsName, realClsName;
- bool isByRef;
- var pt = p.ParameterType.IsByRef ? p.ParameterType.GetElementType() : p.ParameterType;
- pt.GetClassName(out clsName, out realClsName, out isByRef);
- sb.AppendLine(string.Format(" ptr_of_this_method = ILIntepreter.Minus(__esp, {0});", param.Length - j + 1));
- if (p.ParameterType.IsByRef)
- {
- sb.AppendLine(@" switch(ptr_of_this_method->ObjectType)
- {
- case ObjectTypes.StackObjectReference:
- {
- var ___dst = ILIntepreter.ResolveReference(ptr_of_this_method);");
- if (pt.IsValueType && !pt.IsPrimitive && valueTypeBinders != null && valueTypeBinders.Contains(pt))
- {
- sb.AppendLine(string.Format(" if (ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder != null) {{", clsName));
- sb.AppendLine(string.Format(" ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder.WriteBackValue(__domain, ptr_of_this_method, __mStack, ref {1});", clsName, p.Name));
- sb.AppendLine(" } else {");
- pt.GetRefWriteBackValueCode(sb, p.Name);
- sb.AppendLine(" }");
- }
- else
- {
- pt.GetRefWriteBackValueCode(sb, p.Name);
- }
- sb.Append(@" }
- break;
- case ObjectTypes.FieldReference:
- {
- var ___obj = __mStack[ptr_of_this_method->Value];
- if(___obj is ILTypeInstance)
- {
- ((ILTypeInstance)___obj)[ptr_of_this_method->ValueLow] = @");
- sb.Append(p.Name);
- sb.Append(@";
- }
- else
- {
- var ___type = __domain.GetType(___obj.GetType()) as CLRType;
- ___type.SetFieldValue(ptr_of_this_method->ValueLow, ref ___obj, @");
- sb.Append(p.Name);
- sb.Append(@");
- }
- }
- break;
- case ObjectTypes.StaticFieldReference:
- {
- var ___type = __domain.GetType(ptr_of_this_method->Value);
- if(___type is ILType)
- {
- ((ILType)___type).StaticInstance[ptr_of_this_method->ValueLow] = @");
- sb.Append(p.Name);
- sb.Append(@";
- }
- else
- {
- ((CLRType)___type).SetStaticFieldValue(ptr_of_this_method->ValueLow, @");
- sb.Append(p.Name);
- sb.Append(@");
- }
- }
- break;
- case ObjectTypes.ArrayReference:
- {
- var instance_of_arrayReference = __mStack[ptr_of_this_method->Value] as ");
- sb.Append(realClsName);
- sb.Append(@"[];
- instance_of_arrayReference[ptr_of_this_method->ValueLow] = @");
- sb.Append(p.Name);
- sb.AppendLine(@";
- }
- break;
- }");
- sb.AppendLine();
- }
- else if(pt.IsValueType && !pt.IsPrimitive)
- {
- sb.AppendLine(" __intp.FreeStackValueType(ptr_of_this_method);");
- }
- sb.AppendLine(" __intp.Free(ptr_of_this_method);");
- }
- if (!i.IsStatic && ((type.IsValueType && !type.IsPrimitive) || hasByRef))//need to write back value type instance
- {
- sb.AppendLine(string.Format(" ptr_of_this_method = ILIntepreter.Minus(__esp, {0});", paramCnt));
- bool noWriteback = noUnbox;
- if (type.IsValueType && !type.IsPrimitive && !noWriteback)
- {
- if (valueTypeBinders != null && valueTypeBinders.Contains(type))
- {
- string clsName, realClsName;
- bool isByRef;
- type.GetClassName(out clsName, out realClsName, out isByRef);
- sb.AppendLine(string.Format(" if (ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder != null) {{", clsName));
- sb.AppendLine(string.Format(" ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder.WriteBackValue(__domain, ptr_of_this_method, __mStack, ref instance_of_this_method);", clsName));
- sb.AppendLine(" } else {");
- sb.AppendLine(" WriteBackInstance(__domain, ptr_of_this_method, __mStack, ref instance_of_this_method);");
- sb.AppendLine(" }");
- }
- else
- {
- sb.AppendLine(" WriteBackInstance(__domain, ptr_of_this_method, __mStack, ref instance_of_this_method);");
- }
- sb.AppendLine();
- }
- sb.AppendLine(" __intp.Free(ptr_of_this_method);");
- }
-
- if (i.ReturnType != typeof(void))
- {
- if (i.ReturnType.IsValueType && !i.ReturnType.IsPrimitive && valueTypeBinders != null && valueTypeBinders.Contains(i.ReturnType))
- {
- string clsName, realClsName;
- bool isByRef;
- i.ReturnType.GetClassName(out clsName, out realClsName, out isByRef);
- sb.AppendLine(string.Format(" if (ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder != null) {{", clsName));
- sb.AppendLine(string.Format(" ILRuntime.Runtime.Generated.CLRBindings.s_{0}_Binder.PushValue(ref result_of_this_method, __intp, __ret, __mStack);", clsName));
- sb.AppendLine(" return __ret + 1;");
- sb.AppendLine(" } else {");
- i.ReturnType.GetReturnValueCode(sb, domain);
- sb.AppendLine(" }");
- }
- else
- {
- i.ReturnType.GetReturnValueCode(sb, domain);
- }
- }
- else
- sb.AppendLine(" return __ret;");
- sb.AppendLine(" }");
- sb.AppendLine();
- idx++;
- }
- return sb.ToString();
- }
- }
- }
|