プロローグ
最近、テキストをテンプレートに基づいて作成したレターの大量メール送信の問題が発生しました。テンプレートには、静的コンテンツに加えて、受信者とテキストの断片に関する情報があります。 私の場合、これは購読者に新しい記事の公開について自動的に通知するためのテンプレートです;したがって、受信者にアピールし、公開への美しくデザインされたリンクを持っています。
すぐに質問が発生しました-これを実装する方法は? テンプレートに一定の値を設定することから始まり、与えられたモデルデータで置き換えられ、本格的なRazorビューで終わる(サイトはMVC 5上に構築されます)さまざまなソリューションが思い浮かびました。
私との短い戦いの後、私はこのかなり一般的なタスクを完全に解決する時であり、その解決策はそれほど複雑であってはならないという結論に達しました(つまり、.NET Frameworkの一部ではないライブラリに依存すべきではありません) 4)しかし、同時にタスクを解決するのに十分機能的であり、拡張性のマージンがあります。
この記事では、これらの要件を満たすバイトコードジェネレーターに基づいたソリューションと、最も興味深いコードについてコメントします。
テンプレートエンジンのみに興味がある場合は、以下のリンクをご覧ください。
SourceForgeのプロジェクトのテンプレートエンジン(Genesis.Patternizer)およびテストコンソールのソースコード: https ://sourceforge.net/projects/open-genesis/?source=navbar
または、1つのファイルのアーカイブ: Patternizer.zip
問題の声明
まず、構文を決定しましょう。 個人的には、単純な値をフォーマットするために広く使用されているstring.Format関数が好きです。 その構文を使用して、値がテンプレートに挿入される場所を示します。
'{' <> [ ':' < > ] [ '|' < > ] '}'
例:{User.GetFIO()}、{User.Name | user}、{User.Birthdate:dd.MM.yyyy}、{User.Score:0.00 |何もありません}。
目的の値がnullまたはまったくない場合、デフォルト値が置き換えられます。 指定されたプロパティ/フィールド/メソッドがモデルで見つからない場合。 二重中括弧を使用して(string.Format関数のように)中括弧をエスケープし、フォーマット文字列の文字をエスケープします。デフォルト値はスラッシュです。
また、テスト例で使用される既製のテンプレートの例を次に示します。
, {User.GetFIO()|}! , : function PrintMyName() {{ Console.WriteLine("My name is {{0}}. I'm {{1}}.", "{UserName|}", {User.Age:0}); }} {Now:dd MMMM yyyy} {Now:HH:mm:ss}
最初は、テンプレートはモデルのパブリックプロパティのみをサポートすると想定していましたが、開発プロセス中に、フィールドとメソッド(文字列、数値、ブール型、nullなどの引数を渡す可能性がある)および任意の次元の配列へのアクセスのサポートが追加されました。 つまり 次の式も有効なパターンになります。
: {User.Account[0].GetSomeArrayMethod("a", true, 8.5, null)[5,8].Length:0000|NULL}
解決策
パーサー
最初に、テキストテンプレートをどうするかを理解する必要があります。 もちろん、モデル置換の呼び出しごとにテンプレートデータを分析し、値を検索して置換することができます。 しかし、これは非常に遅い方法です。 テンプレートを一度個別の論理フラグメント(テンプレート要素)に解析してから、将来これらの要素を操作する方がはるかに効率的です。 3つの明らかな要素タイプがあります: 文字列定数 (結果に直接変化しないテンプレートの部分)、 置換 (中括弧内)、 コメント (この要素は実装されていませんが、これが何であるかを理解していると思います) )
これらの考慮事項に基づいて、テンプレート要素の基本クラスを説明します。
/// <summary> /// /// </summary> public abstract class PatternElement { /// <summary> /// /// </summary> public virtual int EstimatedLength { get { return 0; } } /// <summary> /// /// </summary> public abstract string GetNullValue(); }
EstimatedLengthプロパティとGetNullValue()メソッドの意味を以下に説明します。
次に、特定の実装-文字列定数と置換(「式」と呼びます)について説明します。
public class StringConstantElement : PatternElement { public string Value { get; set; } public override int EstimatedLength { get { return Value == null ? 0 : Value.Length; } } public override string GetNullValue() { return Value; } } public class ExpressionElement : PatternElement { public string Path { get; set; } public string FormatString { get; set; } public string DefaultValue { get; set; } public override int EstimatedLength { get { return Math.Max(20, DefaultValue == null ? 0 : DefaultValue.Length); } } public override string GetNullValue() { return DefaultValue; } }
また、入力時にテキストテンプレートを受け入れ、一連の要素を発行するIPatternParserパーサーのインターフェイスについても説明します。
public interface IPatternParser { IEnumerable<PatternElement> Parse(string pattern); }
中括弧BracePatternParserに基づいてパーサーを呼び出します。 パーサーの作成経験があまりないため(パーサーが本質的に行っていることです)、その実装には深く入りません。
BracePatternParser.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Genesis.Patternizer { /// <summary> /// /// </summary> public class BracePatternParser : IPatternParser { private object _lock = new object(); private HashSet<char> PATH_TERMINATOR_CHARS; private HashSet<char> FORMAT_TERMINATOR_CHARS; private HashSet<char> PATTERN_TERMINATOR_CHARS; private string pattern; // private int length; // private int length_1; // private int index; // private StringBuilder constantBuilder; private StringBuilder expressionBuilder; /// <summary> /// /// </summary> public BracePatternParser() { PATH_TERMINATOR_CHARS = new HashSet<char>(":|}".ToCharArray()); FORMAT_TERMINATOR_CHARS = new HashSet<char>("|}".ToCharArray()); PATTERN_TERMINATOR_CHARS = new HashSet<char>("}".ToCharArray()); } /// <summary> /// /// </summary> /// <param name="chars"> - </param> /// <returns></returns> private string ParsePatternPath(HashSet<char> chars) { // expressionBuilder.Clear(); Stack<char> brackets = new Stack<char>(); bool ignoreBrackets = false; for (index++; index < length; index++) { char c = pattern[index]; if (c == '(') { brackets.Push(c); expressionBuilder.Append(c); } else if (c == ')') { if (brackets.Peek() == '(') { brackets.Pop(); } else { // ignoreBrackets = true; } expressionBuilder.Append(c); } else if (c == '[') { brackets.Push(c); expressionBuilder.Append(c); } else if (c == ']') { if (brackets.Peek() == '[') { brackets.Pop(); } else { // ignoreBrackets = true; } expressionBuilder.Append(c); } else if (chars.Contains(c) && (ignoreBrackets || brackets.Count == 0)) { // break; } else { expressionBuilder.Append(c); } } return expressionBuilder.Length == 0 ? null : expressionBuilder.ToString(); } /// <summary> /// /// </summary> /// <param name="chars"> - </param> /// <returns></returns> private string ParsePatternPart(HashSet<char> chars) { // expressionBuilder.Clear(); for (index++; index < length; index++) { char c = pattern[index]; if (c == '\\') { // if (index < length_1) { expressionBuilder.Append(pattern[++index]); } } else if (chars.Contains(c)) { // break; } else { expressionBuilder.Append(c); } } return expressionBuilder.Length == 0 ? null : expressionBuilder.ToString(); } /// <summary> /// /// </summary> /// <returns></returns> private ExpressionElement ParsePattern() { string path = ParsePatternPath(PATH_TERMINATOR_CHARS); if (path == null) { // // (}) for (; index < length; index++) { char c = pattern[index]; if (c == '\\') { index++; } else if (c == '}') { break; } } return null; } else { ExpressionElement element = new ExpressionElement(path); // if (index < length && pattern[index] == ':') { // element.FormatString = ParsePatternPart(FORMAT_TERMINATOR_CHARS); } if (index < length && pattern[index] == '|') { // element.DefaultValue = ParsePatternPart(PATTERN_TERMINATOR_CHARS); } return element; } } /// <summary> /// /// </summary> /// <param name="pattern"> </param> /// <returns></returns> public IEnumerable<PatternElement> Parse(string pattern) { lock (_lock) { if (pattern == null) { // yield break; } else if (string.IsNullOrWhiteSpace(pattern)) { yield return new StringConstantElement(pattern); yield break; } // this.pattern = pattern; // length = pattern.Length; length_1 = length - 1; index = 0; // constantBuilder = new StringBuilder(); expressionBuilder = new StringBuilder(); // for (; index < length; index++) { char c = pattern[index]; if (c == '{') { if (index < length_1 && pattern[index + 1] == c) { // '{' constantBuilder.Append(c); index++; } else { // if (constantBuilder.Length != 0) { yield return new StringConstantElement(constantBuilder.ToString()); // constantBuilder.Clear(); } var patternElement = ParsePattern(); if (patternElement != null) { yield return patternElement; } } } else if (c == '}') { if (index < length_1 && pattern[index + 1] == c) { // '}' constantBuilder.Append(c); index++; } else { // , constantBuilder.Append(c); } } else { constantBuilder.Append(c); } } // if (constantBuilder.Length != 0) { yield return new StringConstantElement(constantBuilder.ToString()); } // this.pattern = null; constantBuilder = null; expressionBuilder = null; index = length = length_1 = 0; } } } }
ビルダージェネレーター
上記のパーサーは、一般的なタスクの一部のみを実行します。 テンプレート要素のセットを取得するだけでは不十分であり、それらを処理する必要があります。 これを行うために、システムの主要コンポーネントであるIBuilderGeneratorを表す別のインターフェイスについて説明します。
public interface IBuilderGenerator { Func<object, string> GenerateBuilder(List<PatternElement> pattern, Type modelType); }
最高のパフォーマンスを達成するために、新しいタイプのモデル( modelType )ごとに新しいビルダーを作成し、それをハッシュに書き込みます。 ビルダー自体は、入力でオブジェクト(モデル)を取得し、文字列(完成したテンプレート)を返す通常の関数です。 このインターフェイスの具体的な実装を以下に示し、その前に、すべてをリンクするシステムの最後のコンポーネントを検討します。
テンプレートエンジン
テンプレートエンジン自体は、テンプレート、パーサー、およびビルダーを接続するクラスです。 また、そのコードは、非常に興味深いものではありません。
Patternizator.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using BUILDER = System.Func<object, string>; namespace Genesis.Patternizer { /// <summary> /// - /// </summary> public class Patternizator { #region Declarations private PatternizatorOptions _options; // private string _pattern; // private List<PatternElement> _elements; // private Dictionary<Type, BUILDER> _builders; // #endregion #region Properties /// <summary> /// /// </summary> public string Pattern { get { return _pattern; } set { _pattern = value; PreparePattern(); } } #endregion #region Constructors /// <summary> /// /// </summary> public Patternizator() { _options = PatternizatorOptions.Default; _builders = new Dictionary<Type, BUILDER>(); } /// <summary> /// /// </summary> /// <param name="pattern"> </param> public Patternizator(string pattern) { _options = PatternizatorOptions.Default; Pattern = pattern; } /// <summary> /// /// </summary> /// <param name="options"> </param> public Patternizator(PatternizatorOptions options) { _options = options; _builders = new Dictionary<Type, BUILDER>(); } /// <summary> /// /// </summary> /// <param name="pattern"> </param> /// <param name="options"> </param> public Patternizator(string pattern, PatternizatorOptions options) { _options = options; Pattern = pattern; } #endregion #region Private methods /// <summary> /// /// </summary> private void PreparePattern() { // _elements = _options.Parser.Parse(_pattern).ToList(); // _builders = new Dictionary<Type, BUILDER>(); // //string template = string.Join(Environment.NewLine, _elements.Select(e => System.Text.RegularExpressions.Regex.Replace(e.ToString(), @"\s+", " ").Trim()).ToArray()); } #endregion #region Public methods /// <summary> /// /// </summary> /// <param name="model"> </param> /// <returns></returns> public string Generate(object model) { // Type modelType = model == null ? null : model.GetType(); Type modelTypeKey = modelType ?? typeof(DBNull); // BUILDER builder; if (!_builders.TryGetValue(modelTypeKey, out builder)) { // builder = _options.BuilderGenerator.GenerateBuilder(_elements, modelType); _builders.Add(modelTypeKey, builder); } // return builder(model); } #endregion } }
PatternizatorOptions.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Genesis.Patternizer { /// <summary> /// /// </summary> public class PatternizatorOptions { /// <summary> /// /// </summary> public IPatternParser Parser { get; set; } /// <summary> /// /// </summary> public IBuilderGenerator BuilderGenerator { get; set; } #region Default private static PatternizatorOptions _default; /// <summary> /// /// </summary> public static PatternizatorOptions Default { get { if (_default == null) { _default = new PatternizatorOptions { Parser = new BracePatternParser(), BuilderGenerator = new ReflectionBuilderGenerator(), }; } return _default; } } #endregion } }
オプション( PatternizatorOptions )は、たとえば標準の構文とは異なるテンプレート構文を使用する場合に、パーサーまたはビルダージェネレーターの特定の実装を使用するようにテンプレートエンジンに指示できるオプションの引数です。
標準としてテンプレートエンジンを使用する例:
// string pattern = GetPattern(); // Patternizator patternizator = new Patternizator(pattern); // User user = new User { Surname = RandomElement(rnd, SURNAMES), Name = RandomElement(rnd, NAMES), Patronymic = RandomElement(rnd, PATRONYMICS), // 1950 - 1990 Birthdate = new DateTime(1950, 1, 1).AddDays(rnd.NextDouble() * 40.0 * 365.25) }; var model = new { User = user, UserName = user.Name, Now = DateTime.Now, }; // string text = patternizator.Generate(model);
この例では、モデルは匿名型ですが、これで混乱することはありません。 ループでこのタイプの要素を生成する場合でも、ビルダーは、Generateメソッドが最初に呼び出されたときに一度だけ作成されます。 しかし、記事の最後でパフォーマンスの問題に戻ります。次に、この出版物の最も興味深い、いわば爪を見てみましょう。
バイトコードビルダージェネレーター
まず、小さな分析を行います。 理論上、この問題はどのように解決できますか?
テンプレート要素(定数と式)とモデルのタイプのリストがあることを思い出させてください。 また、関数Func <object、string>を取得する必要があります。この関数は、テンプレートで指定されたタイプのモデルを置き換え、出力で文字列を受け取ります。
定数に関する質問がない場合(StringBuilderで投げるだけです)、式ではすべてがより複雑になります。
モデルから式の値を取得する方法には、3つの可能なオプションがあります。
- 反射を通して
- C#コードを生成し、コンパイルしてアセンブリに接続します
- バイトコードの本文を使用して動的関数( System.Reflection.Emit.DynamicMethod )を記述します
最初のオプションは明らかに遅いです、なぜなら 反射は常にゆっくりと働きます。 あなたへの私のアドバイスは、頻繁に実行される操作にリフレクションを使用しないことです。 その使用に最適なオプションは、プログラムの起動段階での準備です。 「クラスを駆け抜け、属性に必要な情報を見つけ、ある種の接続(デリゲート、イベント)を構築してから、再びリフレクションに頼らずにそれらを使用する」などです。 一般的に、反射は明らかにこのタスクには適していません。
2番目のオプションは非常に優れており、最初は使用したかったです。 ビルダー関数のコードは次のようになります(最初のテンプレートの場合):
public string Generate(object input) { if (input == null) { // return @", ! , : function PrintMyName() { Console.WriteLine("My name is {0}. I'm {1}.", "", 0); } "; } else { Model model = input as Model; StringBuilder sb = new StringBuilder(); sb.Append(", "); // if (model.User != null) { var m_GetFIO = model.User.GetFIO(); if (m_GetFIO != null) { sb.Append(m_GetFIO); } else { sb.Append(""); // } } else { sb.Append(""); // } sb.Append("!\r\n , :\r\n\r\n ..."); // \\ .. return sb.ToString(); } }
もちろん、コードは十分に長くなりますが、誰が見るのでしょうか? 一般に、このオプションは、匿名型でない場合に最適です。 上記のコードでは、モデル変数Model model = inputを<?>として宣言できませんでした。 モデルタイプに名前がなかった場合。
したがって、3番目のオプションは残ります。 同じコードをバイトコードで直接生成します。 テンプレートエンジンを作成するとき、私自身が初めて動的関数とバイトコードジェネレーターを使用しました。これが、この技術を習得する際に、親愛なる読者の方が問題が少なくなるようにこの記事を書くきっかけになりました。
動的アセンブリ、動的関数、およびバイトコードジェネレーターはSystem.Reflection.Emit名前空間に記述されており、それらを使用するために追加のライブラリを接続する必要はありません。
最も単純な動的関数は次のように作成されます。
// var genMethod = new DynamicMethod("< >", typeof(< >), new Type[] { typeof(< 1>), typeof(< 2>), ..., typeof(< N>) }, true); // - ( IL- CIL-) var cs = genMethod.GetILGenerator(); // // ... // cs.Emit(OpCodes.Ret); // return genMethod.CreateDelegate(typeof(< >)) as < >;
cs.Emit(OpCodes.Ret); -これは、コマンドをバイトコードで書き込む操作です。 知らない人は誰でも、 バイトコードは.NET言語のアセンブラーのようなものです。
強さを集めてこの段落の前の記事を読んだ場合、質問があります。コマンドがわからない場合はどうすればバイトコードを生成できますか? 答えは非常に簡単です。 これを行うには、追加のプログラムが必要です(プロジェクトはアーカイブ内にあります)。そのコードはネタバレの下で提供されます。
ILDasm
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; namespace ILDasm { class Program { #region Static static void Main(string[] args) { new Program().Run(); } #endregion public void Run() { string basePath = AppDomain.CurrentDomain.BaseDirectory; string exeName = Path.Combine(basePath, AppDomain.CurrentDomain.FriendlyName.Replace(".vshost", "")); Process.Start(@"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\x64\ildasm.exe", string.Format(@"/item:ILDasm.TestClass::DoIt ""{0}"" /text /output:code.il", exeName)); } } public class TestClass { public string DoIt(object value) { StringBuilder sb = new StringBuilder(); return sb.ToString(); } } }
プログラムの意味は、スタジオに組み込まれたildasm逆アセンブラーを実行し、 TestClassクラスのDoIt関数に対して設定することです。 この関数の本体のバイトコードはcode.ilファイルに配置され、開いて分析できます。 DoIt関数のバイトコードを持ち込みます(追加削除):
IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: callvirt instance string [mscorlib]System.Object::ToString() IL_000c: ret
試行錯誤の方法と組み合わされた頭蓋骨ボックス内の物質は、類推によってコードを生成するのに役立ちます。 生成された関数に必要なもののようにDoIt関数の本体を記述し、ユーティリティを実行し、コードを見てジェネレーターに実装します。
バイトコードの一般情報
すべてがスタック上に構築されます。
加算演算aとbを実行する場合、変数aの値をスタックにプッシュし、変数bの値をスタックにプッシュしてから、addコマンドを呼び出す必要があります。 この場合、スタックのaとbがクリアされ、加算の結果が最上部に配置されます。 この後、合計にcを乗算し、その値をスタックに置き(現在、すでに合計a + bがあることに注意してください)、乗算演算(mul)を呼び出します。
結果のバイトコード:
IL_0000: ldarg.1 IL_0001: ldarg.2 IL_0002: add IL_0003: ldarg.3 IL_0004: mul
そして、C#での表示は次のとおりです。
cs.Emit(OpCodes.Ldarg_1); cs.Emit(OpCodes.Ldarg_2); cs.Emit(OpCodes.Add); cs.Emit(OpCodes.Ldarg_3); cs.Emit(OpCodes.Mul);
メソッドとコンストラクターは同じ方法で呼び出されます(引数をスタックに置き、メソッド/コンストラクターを呼び出します)。 この場合、非静的メソッドの場合、最初にスタックに配置するのは、メソッドを呼び出しているクラスのインスタンスです。
この記事は、バイトコード生成の完全なトレーニングを提供することを目的としていないため、ジェネレーターの説明を続けましょう。
ジェネレーターコアは、そのインターフェイスを実装する関数( IBuilderGenerator )で囲まれています。
GenerateBuilder
/// <summary> /// /// </summary> /// <param name="pattern"> </param> /// <param name="modelType"> </param> /// <returns></returns> public virtual BUILDER GenerateBuilder(List<PatternElement> pattern, Type modelType) { if (modelType == null) { // , StringBuilder sb = new StringBuilder(); foreach (PatternElement item in pattern) { string nullValue = item.GetNullValue(); if (nullValue != null) { sb.Append(nullValue); } } string value = sb.ToString(); return (m) => value; } else { // string methodName = "Generate_" + Guid.NewGuid().ToString().Replace("-", ""); // var genMethod = new DynamicMethod(methodName, typeof(string), new Type[] { typeof(object) }, true); // var cs = genMethod.GetILGenerator(); var sb = cs.DeclareLocal(typeof(StringBuilder)); var m = cs.DeclareLocal(modelType); ReflectionBuilderGeneratorContext context = new ReflectionBuilderGeneratorContext { Generator = cs, ModelType = modelType, VarSB = sb, VarModel = m, }; // cs.Emit(OpCodes.Ldarg_0); cs.Emit(OpCodes.Isinst, modelType); cs.Emit(OpCodes.Stloc, m); // StringBuilder cs.Emit(OpCodes.Ldc_I4, pattern.Sum(e => e.EstimatedLength)); cs.Emit(OpCodes.Newobj, typeof(StringBuilder).GetConstructor(new Type[] { typeof(int) })); cs.Emit(OpCodes.Stloc, sb); foreach (PatternElement item in pattern) { MethodInfo processor; if (_dicProcessors.TryGetValue(item.GetType(), out processor)) { // processor.Invoke(processor.IsStatic ? null : this, new object[] { context, item }); } } cs.Emit(OpCodes.Ldloc, sb); cs.Emit(OpCodes.Callvirt, typeof(object).GetMethod("ToString", Type.EmptyTypes)); cs.Emit(OpCodes.Ret); return genMethod.CreateDelegate(typeof(BUILDER)) as BUILDER; } }
これが、テンプレート要素のGetNullValue()メソッドとEstimatedLengthプロパティが便利な場所です。
ジェネレーターの特徴は、その拡張性です。 冒頭で説明したテンプレート要素のタイプ(文字列定数と式)には関係ありません。 必要に応じて、独自の要素を考え出し、このジェネレーターを継承して、作成した要素のタイプのバイトコードを生成する機能を追加できます。 これを行うには、コードでPatternElementAttribute属性を使用して関数を記述する必要があります。たとえば、標準ジェネレーターの実装に含まれる文字列定数のコード生成は次のように記述されます。
[PatternElement(typeof(StringConstantElement))] protected virtual void GenerateStringConstantIL(ReflectionBuilderGeneratorContext context, StringConstantElement element) { if (element.Value != null) { WriteSB_Constant(context, element.Value); } } /// <summary> /// StringBuilder /// </summary> /// <param name="context"> </param> /// <param name="value"> </param> protected virtual void WriteSB_Constant(ReflectionBuilderGeneratorContext context, string value) { if (value != null) { var cs = context.Generator; cs.Emit(OpCodes.Ldloc, context.VarSB); cs.Emit(OpCodes.Ldstr, value); cs.Emit(OpCodes.Callvirt, _dicStringBuilderAppend[typeof(string)]); cs.Emit(OpCodes.Pop); } }
他のメソッドのコードは提供しません とても面倒ですが、質問があれば、個別に答えようとします。
性能試験
なぜなら テンプレートエンジンを他のテンプレートエンジンと比較する機会はありません。string.Replace()に基づくハードコードテンプレートジェネレーターと比較します。
テスト機能コード
/// <summary> /// /// </summary> private void Run() { // string outputPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Result"); if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath); Random rnd = new Random(0); Stopwatch sw = new Stopwatch(); // string pattern = GetPattern(); // string text; double patternTotal = 0; // () double patternInitialization; // () double patternFirst = 0; // () double manualTotal = 0; // () // sw.Restart(); Patternizator patternizator = new Patternizator(pattern); sw.Stop(); patternInitialization = sw.Elapsed.TotalMilliseconds; Console.WriteLine(" {0} (v. {1})", patternizator.GetType().Assembly.GetName().Name, patternizator.GetType().Assembly.GetName().Version); // for (int i = 0; i < COUNT_PATTERNIZATOR; i++) { // User user = new User { Surname = RandomElement(rnd, SURNAMES), Name = RandomElement(rnd, NAMES), Patronymic = RandomElement(rnd, PATRONYMICS), // 1950 - 1990 Birthdate = new DateTime(1950, 1, 1).AddDays(rnd.NextDouble() * 40.0 * 365.25) }; var model = new { User = user, UserName = user.Name, Now = DateTime.Now, }; // sw.Restart(); text = patternizator.Generate(model); sw.Stop(); patternTotal += sw.Elapsed.TotalMilliseconds; if (i == 0) { patternFirst = sw.Elapsed.TotalMilliseconds; } // if (i < COUNT_MANUAL) { // ! // - Replace sw.Restart(); { StringBuilder sb = new StringBuilder(pattern); DateTime now = DateTime.Now; sb.Replace("{User.GetFIO()|}", model.User.GetFIO() ?? ""); sb.Replace("{UserName|}", model.UserName ?? ""); sb.Replace("{User.Age:0}", model.User.Age.ToString("0")); sb.Replace("{Now:dd MMMM yyyy}", now.ToString("dd MMMM yyyy")); sb.Replace("{Now:HH:mm:ss}", now.ToString("HH:mm:ss")); text = sb.ToString(); } sw.Stop(); manualTotal += sw.Elapsed.TotalMilliseconds; } } WriteHeader(""); WriteElapsedTime(" ", patternInitialization); WriteElapsedTime(" ", patternFirst); Console.WriteLine(); WriteElapsedTime(string.Format(" {0} ", COUNT_PATTERNIZATOR), patternTotal); WriteElapsedTime(" ", patternTotal / COUNT_PATTERNIZATOR); WriteHeader(" ()"); WriteElapsedTime(string.Format(" {0} ", COUNT_MANUAL), manualTotal); WriteElapsedTime(" ", manualTotal / COUNT_MANUAL); Console.WriteLine(); Console.WriteLine(" ..."); Console.ReadKey(); }
スクリーンショット:
結論の代わりに
この記事は大きく、多すぎるかもしれないことがわかったが、そこで取り上げられたトピックはそれほど単純ではない。したがって、バイトコード生成のどの質問をより詳細に知りたいのか、購読を停止するようお願いします。これらの問題については、別の記事を書くようにします。