[{"data":1,"prerenderedAt":3227},["ShallowReactive",2],{"content-query-cXN6yMBwTS":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"tags":11,"rowTypeId":17,"sitemap":18,"body":19,"_type":3221,"_id":3222,"_source":3223,"_file":3224,"_stem":3225,"_extension":3226},"/articles/tech/blazor/undo-redo-command-pattern","blazor",false,"","StackベースのUndo/RedoをC#で実装する｜CompositeCommandで複数操作も一括取り消し","楽譜エディタのUndo/RedoをCommandパターンで実装した実例を紹介します。2本のStackで履歴を管理し、CompositeCommandで複数操作を1手としてまとめ、TrimHistoryで履歴上限を設ける方法を解説します。","2026-04-17",[12,13,14,15,16],"C#","デザインパターン","Command","Blazor","PICOM",1,{"loc":4,"lastmod":10,"priority":17},{"type":20,"children":21,"toc":3212},"root",[22,30,36,50,82,88,93,106,112,117,376,427,432,895,921,927,954,1862,1867,1887,1922,1928,1933,1946,2237,2264,2334,2355,2361,2388,2393,2926,2938,2943,3137,3142,3147,3206],{"type":23,"tag":24,"props":25,"children":27},"element","h2",{"id":26},"はじめに",[28],{"type":29,"value":26},"text",{"type":23,"tag":31,"props":32,"children":33},"p",{},[34],{"type":29,"value":35},"楽譜エディタ「PICOM」を作り始めて、Undo/Redoがないと不便だと感じ実装しました。おそらく、多くの編集系UIを持つアプリでは必須機能だと思います。",{"type":23,"tag":31,"props":37,"children":38},{},[39,41,48],{"type":29,"value":40},"そこで、この記事では、PICOMで実装したUndo/Redoの仕組みを紹介します。教科書通りの",{"type":23,"tag":42,"props":43,"children":45},"tooltip",{"content":44},"実行する処理をオブジェクトとしてカプセル化し、履歴管理や取り消しを可能にするデザインパターン",[46],{"type":29,"value":47},"Commandパターン",{"type":29,"value":49},"ですが、実際に使えるものにするには「複数操作を1手としてまとめたい」「履歴が無限に増えるのは困る」といった現実的な課題があります。そのあたりをどう解決したかを具体的に書きます。",{"type":23,"tag":51,"props":52,"children":53},"summary-box",{},[54],{"type":23,"tag":31,"props":55,"children":56},{},[57,64,66,72,74,80],{"type":23,"tag":58,"props":59,"children":61},"code",{"className":60},[],[62],{"type":29,"value":63},"ICommand",{"type":29,"value":65},"インターフェースを定義し、",{"type":23,"tag":58,"props":67,"children":69},{"className":68},[],[70],{"type":29,"value":71},"UndoRedoManager",{"type":29,"value":73},"が2本のStackで履歴を管理します。",{"type":23,"tag":58,"props":75,"children":77},{"className":76},[],[78],{"type":29,"value":79},"CompositeCommand",{"type":29,"value":81},"で複数のコマンドを1手にまとめ、ピアノロール上の複数音符を同時に移動するような操作も一発でUndoできます。履歴には上限を設けてメモリリークを防いでいます。",{"type":23,"tag":24,"props":83,"children":85},{"id":84},"undoredoの仕組みを考える",[86],{"type":29,"value":87},"Undo/Redoの仕組みを考える",{"type":23,"tag":31,"props":89,"children":90},{},[91],{"type":29,"value":92},"Undo/Redoを愚直に実装する方法として、操作をするたびに状態を時系列に沿って記憶するという方法が思いつきます。しかし、この方法ではメモリ効率も悪く、状態管理も非常に複雑化します。",{"type":23,"tag":31,"props":94,"children":95},{},[96,98,104],{"type":29,"value":97},"そこで、デザインパターンであるCommandパターンを利用します。Commandパターンでは、状態ではなく ",{"type":23,"tag":99,"props":100,"children":101},"strong",{},[102],{"type":29,"value":103},"操作",{"type":29,"value":105}," を記憶するため、以前の状態に復元したり、次の状態へ進めたりなどが容易に実現できます。\n続いて、実際の実装を見ていきましょう。",{"type":23,"tag":24,"props":107,"children":109},{"id":108},"icommand最小インターフェースから始める",[110],{"type":29,"value":111},"ICommand：最小インターフェースから始める",{"type":23,"tag":31,"props":113,"children":114},{},[115],{"type":29,"value":116},"Commandパターンの出発点は「実行」と「取り消し」を1ペアで持つインターフェースです。PICOMでは次のように定義しています。",{"type":23,"tag":118,"props":119,"children":123},"pre",{"className":120,"code":121,"language":122,"meta":7,"style":7},"language-csharp shiki shiki-themes vitesse-dark","public interface ICommand\n{\n    /// \u003Csummary>コマンドを実行する\u003C/summary>\n    void Execute();\n\n    /// \u003Csummary>コマンドを取り消す\u003C/summary>\n    void Undo();\n\n    /// \u003Csummary>コマンドの説明（デバッグ/UI表示用）\u003C/summary>\n    string Description { get; }\n}\n","csharp",[124],{"type":23,"tag":58,"props":125,"children":126},{"__ignoreMap":7},[127,149,159,204,224,234,271,288,296,333,367],{"type":23,"tag":128,"props":129,"children":131},"span",{"class":130,"line":17},"line",[132,138,143],{"type":23,"tag":128,"props":133,"children":135},{"style":134},"--shiki-default:#CB7676",[136],{"type":29,"value":137},"public",{"type":23,"tag":128,"props":139,"children":140},{"style":134},[141],{"type":29,"value":142}," interface",{"type":23,"tag":128,"props":144,"children":146},{"style":145},"--shiki-default:#5DA994",[147],{"type":29,"value":148}," ICommand\n",{"type":23,"tag":128,"props":150,"children":152},{"class":130,"line":151},2,[153],{"type":23,"tag":128,"props":154,"children":156},{"style":155},"--shiki-default:#666666",[157],{"type":29,"value":158},"{\n",{"type":23,"tag":128,"props":160,"children":162},{"class":130,"line":161},3,[163,169,174,180,185,190,195,199],{"type":23,"tag":128,"props":164,"children":166},{"style":165},"--shiki-default:#758575DD",[167],{"type":29,"value":168},"    /// ",{"type":23,"tag":128,"props":170,"children":171},{"style":155},[172],{"type":29,"value":173},"\u003C",{"type":23,"tag":128,"props":175,"children":177},{"style":176},"--shiki-default:#4D9375",[178],{"type":29,"value":179},"summary",{"type":23,"tag":128,"props":181,"children":182},{"style":155},[183],{"type":29,"value":184},">",{"type":23,"tag":128,"props":186,"children":187},{"style":165},[188],{"type":29,"value":189},"コマンドを実行する",{"type":23,"tag":128,"props":191,"children":192},{"style":155},[193],{"type":29,"value":194},"\u003C/",{"type":23,"tag":128,"props":196,"children":197},{"style":176},[198],{"type":29,"value":179},{"type":23,"tag":128,"props":200,"children":201},{"style":155},[202],{"type":29,"value":203},">\n",{"type":23,"tag":128,"props":205,"children":207},{"class":130,"line":206},4,[208,213,219],{"type":23,"tag":128,"props":209,"children":210},{"style":176},[211],{"type":29,"value":212},"    void",{"type":23,"tag":128,"props":214,"children":216},{"style":215},"--shiki-default:#80A665",[217],{"type":29,"value":218}," Execute",{"type":23,"tag":128,"props":220,"children":221},{"style":155},[222],{"type":29,"value":223},"();\n",{"type":23,"tag":128,"props":225,"children":227},{"class":130,"line":226},5,[228],{"type":23,"tag":128,"props":229,"children":231},{"emptyLinePlaceholder":230},true,[232],{"type":29,"value":233},"\n",{"type":23,"tag":128,"props":235,"children":237},{"class":130,"line":236},6,[238,242,246,250,254,259,263,267],{"type":23,"tag":128,"props":239,"children":240},{"style":165},[241],{"type":29,"value":168},{"type":23,"tag":128,"props":243,"children":244},{"style":155},[245],{"type":29,"value":173},{"type":23,"tag":128,"props":247,"children":248},{"style":176},[249],{"type":29,"value":179},{"type":23,"tag":128,"props":251,"children":252},{"style":155},[253],{"type":29,"value":184},{"type":23,"tag":128,"props":255,"children":256},{"style":165},[257],{"type":29,"value":258},"コマンドを取り消す",{"type":23,"tag":128,"props":260,"children":261},{"style":155},[262],{"type":29,"value":194},{"type":23,"tag":128,"props":264,"children":265},{"style":176},[266],{"type":29,"value":179},{"type":23,"tag":128,"props":268,"children":269},{"style":155},[270],{"type":29,"value":203},{"type":23,"tag":128,"props":272,"children":274},{"class":130,"line":273},7,[275,279,284],{"type":23,"tag":128,"props":276,"children":277},{"style":176},[278],{"type":29,"value":212},{"type":23,"tag":128,"props":280,"children":281},{"style":215},[282],{"type":29,"value":283}," Undo",{"type":23,"tag":128,"props":285,"children":286},{"style":155},[287],{"type":29,"value":223},{"type":23,"tag":128,"props":289,"children":291},{"class":130,"line":290},8,[292],{"type":23,"tag":128,"props":293,"children":294},{"emptyLinePlaceholder":230},[295],{"type":29,"value":233},{"type":23,"tag":128,"props":297,"children":299},{"class":130,"line":298},9,[300,304,308,312,316,321,325,329],{"type":23,"tag":128,"props":301,"children":302},{"style":165},[303],{"type":29,"value":168},{"type":23,"tag":128,"props":305,"children":306},{"style":155},[307],{"type":29,"value":173},{"type":23,"tag":128,"props":309,"children":310},{"style":176},[311],{"type":29,"value":179},{"type":23,"tag":128,"props":313,"children":314},{"style":155},[315],{"type":29,"value":184},{"type":23,"tag":128,"props":317,"children":318},{"style":165},[319],{"type":29,"value":320},"コマンドの説明（デバッグ/UI表示用）",{"type":23,"tag":128,"props":322,"children":323},{"style":155},[324],{"type":29,"value":194},{"type":23,"tag":128,"props":326,"children":327},{"style":176},[328],{"type":29,"value":179},{"type":23,"tag":128,"props":330,"children":331},{"style":155},[332],{"type":29,"value":203},{"type":23,"tag":128,"props":334,"children":336},{"class":130,"line":335},10,[337,342,347,352,357,362],{"type":23,"tag":128,"props":338,"children":339},{"style":176},[340],{"type":29,"value":341},"    string",{"type":23,"tag":128,"props":343,"children":344},{"style":215},[345],{"type":29,"value":346}," Description",{"type":23,"tag":128,"props":348,"children":349},{"style":155},[350],{"type":29,"value":351}," {",{"type":23,"tag":128,"props":353,"children":354},{"style":134},[355],{"type":29,"value":356}," get",{"type":23,"tag":128,"props":358,"children":359},{"style":155},[360],{"type":29,"value":361},";",{"type":23,"tag":128,"props":363,"children":364},{"style":155},[365],{"type":29,"value":366}," }\n",{"type":23,"tag":128,"props":368,"children":370},{"class":130,"line":369},11,[371],{"type":23,"tag":128,"props":372,"children":373},{"style":155},[374],{"type":29,"value":375},"}\n",{"type":23,"tag":31,"props":377,"children":378},{},[379,381,387,389,395,397,402,404,410,412,418,420,425],{"type":29,"value":380},"ポイントは",{"type":23,"tag":58,"props":382,"children":384},{"className":383},[],[385],{"type":29,"value":386},"Description",{"type":29,"value":388},"を入れていることです。履歴一覧を出したり、デバッグで",{"type":23,"tag":58,"props":390,"children":392},{"className":391},[],[393],{"type":29,"value":394},"Console.WriteLine",{"type":29,"value":396},"する時に、どのコマンドが何をしたのかを文字列で追えると",{"type":23,"tag":99,"props":398,"children":399},{},[400],{"type":29,"value":401},"デバッグの効率が雲泥の差",{"type":29,"value":403},"です。.NET標準の",{"type":23,"tag":58,"props":405,"children":407},{"className":406},[],[408],{"type":29,"value":409},"System.Windows.Input.ICommand",{"type":29,"value":411},"とは別物なので、名前が被る場合は名前空間を明示するかリネームしてください。PICOMはBlazor WASMで実装しているため、",{"type":23,"tag":58,"props":413,"children":415},{"className":414},[],[416],{"type":29,"value":417},"System.Windows",{"type":29,"value":419},"の",{"type":23,"tag":58,"props":421,"children":423},{"className":422},[],[424],{"type":29,"value":63},{"type":29,"value":426},"と衝突することはなかったです。",{"type":23,"tag":31,"props":428,"children":429},{},[430],{"type":29,"value":431},"具体的なコマンドの実装例として、音符追加コマンドを載せておきます。",{"type":23,"tag":118,"props":433,"children":435},{"className":120,"code":434,"language":122,"meta":7,"style":7},"public class AddNoteCommand : ICommand\n{\n    private readonly Track _track;\n    private readonly int _position128Note;\n    private readonly SoundComponentBase _component;\n\n    public string Description => $\"ノート追加 at {_position128Note}\";\n\n    public AddNoteCommand(Track track, int position128Note, SoundComponentBase component)\n    {\n        _track = track;\n        _position128Note = position128Note;\n        _component = component;\n    }\n\n    public void Execute() => _track.AddAt(_position128Note, _component);\n    public void Undo() => _track.RemoveAt(_position128Note, _component);\n}\n",[436],{"type":23,"tag":58,"props":437,"children":438},{"__ignoreMap":7},[439,465,472,500,525,550,557,615,622,680,688,710,731,752,761,769,830,887],{"type":23,"tag":128,"props":440,"children":441},{"class":130,"line":17},[442,446,451,456,461],{"type":23,"tag":128,"props":443,"children":444},{"style":134},[445],{"type":29,"value":137},{"type":23,"tag":128,"props":447,"children":448},{"style":134},[449],{"type":29,"value":450}," class",{"type":23,"tag":128,"props":452,"children":453},{"style":145},[454],{"type":29,"value":455}," AddNoteCommand",{"type":23,"tag":128,"props":457,"children":458},{"style":155},[459],{"type":29,"value":460}," :",{"type":23,"tag":128,"props":462,"children":463},{"style":145},[464],{"type":29,"value":148},{"type":23,"tag":128,"props":466,"children":467},{"class":130,"line":151},[468],{"type":23,"tag":128,"props":469,"children":470},{"style":155},[471],{"type":29,"value":158},{"type":23,"tag":128,"props":473,"children":474},{"class":130,"line":161},[475,480,485,490,495],{"type":23,"tag":128,"props":476,"children":477},{"style":134},[478],{"type":29,"value":479},"    private",{"type":23,"tag":128,"props":481,"children":482},{"style":134},[483],{"type":29,"value":484}," readonly",{"type":23,"tag":128,"props":486,"children":487},{"style":145},[488],{"type":29,"value":489}," Track",{"type":23,"tag":128,"props":491,"children":492},{"style":215},[493],{"type":29,"value":494}," _track",{"type":23,"tag":128,"props":496,"children":497},{"style":155},[498],{"type":29,"value":499},";\n",{"type":23,"tag":128,"props":501,"children":502},{"class":130,"line":206},[503,507,511,516,521],{"type":23,"tag":128,"props":504,"children":505},{"style":134},[506],{"type":29,"value":479},{"type":23,"tag":128,"props":508,"children":509},{"style":134},[510],{"type":29,"value":484},{"type":23,"tag":128,"props":512,"children":513},{"style":176},[514],{"type":29,"value":515}," int",{"type":23,"tag":128,"props":517,"children":518},{"style":215},[519],{"type":29,"value":520}," _position128Note",{"type":23,"tag":128,"props":522,"children":523},{"style":155},[524],{"type":29,"value":499},{"type":23,"tag":128,"props":526,"children":527},{"class":130,"line":226},[528,532,536,541,546],{"type":23,"tag":128,"props":529,"children":530},{"style":134},[531],{"type":29,"value":479},{"type":23,"tag":128,"props":533,"children":534},{"style":134},[535],{"type":29,"value":484},{"type":23,"tag":128,"props":537,"children":538},{"style":145},[539],{"type":29,"value":540}," SoundComponentBase",{"type":23,"tag":128,"props":542,"children":543},{"style":215},[544],{"type":29,"value":545}," _component",{"type":23,"tag":128,"props":547,"children":548},{"style":155},[549],{"type":29,"value":499},{"type":23,"tag":128,"props":551,"children":552},{"class":130,"line":236},[553],{"type":23,"tag":128,"props":554,"children":555},{"emptyLinePlaceholder":230},[556],{"type":29,"value":233},{"type":23,"tag":128,"props":558,"children":559},{"class":130,"line":273},[560,565,570,574,579,585,591,596,601,606,611],{"type":23,"tag":128,"props":561,"children":562},{"style":134},[563],{"type":29,"value":564},"    public",{"type":23,"tag":128,"props":566,"children":567},{"style":176},[568],{"type":29,"value":569}," string",{"type":23,"tag":128,"props":571,"children":572},{"style":215},[573],{"type":29,"value":346},{"type":23,"tag":128,"props":575,"children":576},{"style":134},[577],{"type":29,"value":578}," =>",{"type":23,"tag":128,"props":580,"children":582},{"style":581},"--shiki-default:#C98A7D77",[583],{"type":29,"value":584}," $\"",{"type":23,"tag":128,"props":586,"children":588},{"style":587},"--shiki-default:#C98A7D",[589],{"type":29,"value":590},"ノート追加 at ",{"type":23,"tag":128,"props":592,"children":593},{"style":155},[594],{"type":29,"value":595},"{",{"type":23,"tag":128,"props":597,"children":598},{"style":587},[599],{"type":29,"value":600},"_position128Note",{"type":23,"tag":128,"props":602,"children":603},{"style":155},[604],{"type":29,"value":605},"}",{"type":23,"tag":128,"props":607,"children":608},{"style":581},[609],{"type":29,"value":610},"\"",{"type":23,"tag":128,"props":612,"children":613},{"style":155},[614],{"type":29,"value":499},{"type":23,"tag":128,"props":616,"children":617},{"class":130,"line":290},[618],{"type":23,"tag":128,"props":619,"children":620},{"emptyLinePlaceholder":230},[621],{"type":29,"value":233},{"type":23,"tag":128,"props":623,"children":624},{"class":130,"line":298},[625,629,633,638,643,648,653,657,662,666,670,675],{"type":23,"tag":128,"props":626,"children":627},{"style":134},[628],{"type":29,"value":564},{"type":23,"tag":128,"props":630,"children":631},{"style":215},[632],{"type":29,"value":455},{"type":23,"tag":128,"props":634,"children":635},{"style":155},[636],{"type":29,"value":637},"(",{"type":23,"tag":128,"props":639,"children":640},{"style":145},[641],{"type":29,"value":642},"Track",{"type":23,"tag":128,"props":644,"children":645},{"style":215},[646],{"type":29,"value":647}," track",{"type":23,"tag":128,"props":649,"children":650},{"style":155},[651],{"type":29,"value":652},",",{"type":23,"tag":128,"props":654,"children":655},{"style":176},[656],{"type":29,"value":515},{"type":23,"tag":128,"props":658,"children":659},{"style":215},[660],{"type":29,"value":661}," position128Note",{"type":23,"tag":128,"props":663,"children":664},{"style":155},[665],{"type":29,"value":652},{"type":23,"tag":128,"props":667,"children":668},{"style":145},[669],{"type":29,"value":540},{"type":23,"tag":128,"props":671,"children":672},{"style":215},[673],{"type":29,"value":674}," component",{"type":23,"tag":128,"props":676,"children":677},{"style":155},[678],{"type":29,"value":679},")\n",{"type":23,"tag":128,"props":681,"children":682},{"class":130,"line":335},[683],{"type":23,"tag":128,"props":684,"children":685},{"style":155},[686],{"type":29,"value":687},"    {\n",{"type":23,"tag":128,"props":689,"children":690},{"class":130,"line":369},[691,697,702,706],{"type":23,"tag":128,"props":692,"children":694},{"style":693},"--shiki-default:#BD976A",[695],{"type":29,"value":696},"        _track",{"type":23,"tag":128,"props":698,"children":699},{"style":155},[700],{"type":29,"value":701}," =",{"type":23,"tag":128,"props":703,"children":704},{"style":693},[705],{"type":29,"value":647},{"type":23,"tag":128,"props":707,"children":708},{"style":155},[709],{"type":29,"value":499},{"type":23,"tag":128,"props":711,"children":713},{"class":130,"line":712},12,[714,719,723,727],{"type":23,"tag":128,"props":715,"children":716},{"style":693},[717],{"type":29,"value":718},"        _position128Note",{"type":23,"tag":128,"props":720,"children":721},{"style":155},[722],{"type":29,"value":701},{"type":23,"tag":128,"props":724,"children":725},{"style":693},[726],{"type":29,"value":661},{"type":23,"tag":128,"props":728,"children":729},{"style":155},[730],{"type":29,"value":499},{"type":23,"tag":128,"props":732,"children":734},{"class":130,"line":733},13,[735,740,744,748],{"type":23,"tag":128,"props":736,"children":737},{"style":693},[738],{"type":29,"value":739},"        _component",{"type":23,"tag":128,"props":741,"children":742},{"style":155},[743],{"type":29,"value":701},{"type":23,"tag":128,"props":745,"children":746},{"style":693},[747],{"type":29,"value":674},{"type":23,"tag":128,"props":749,"children":750},{"style":155},[751],{"type":29,"value":499},{"type":23,"tag":128,"props":753,"children":755},{"class":130,"line":754},14,[756],{"type":23,"tag":128,"props":757,"children":758},{"style":155},[759],{"type":29,"value":760},"    }\n",{"type":23,"tag":128,"props":762,"children":764},{"class":130,"line":763},15,[765],{"type":23,"tag":128,"props":766,"children":767},{"emptyLinePlaceholder":230},[768],{"type":29,"value":233},{"type":23,"tag":128,"props":770,"children":772},{"class":130,"line":771},16,[773,777,782,786,791,795,799,804,809,813,817,821,825],{"type":23,"tag":128,"props":774,"children":775},{"style":134},[776],{"type":29,"value":564},{"type":23,"tag":128,"props":778,"children":779},{"style":176},[780],{"type":29,"value":781}," void",{"type":23,"tag":128,"props":783,"children":784},{"style":215},[785],{"type":29,"value":218},{"type":23,"tag":128,"props":787,"children":788},{"style":155},[789],{"type":29,"value":790},"()",{"type":23,"tag":128,"props":792,"children":793},{"style":134},[794],{"type":29,"value":578},{"type":23,"tag":128,"props":796,"children":797},{"style":693},[798],{"type":29,"value":494},{"type":23,"tag":128,"props":800,"children":801},{"style":155},[802],{"type":29,"value":803},".",{"type":23,"tag":128,"props":805,"children":806},{"style":215},[807],{"type":29,"value":808},"AddAt",{"type":23,"tag":128,"props":810,"children":811},{"style":155},[812],{"type":29,"value":637},{"type":23,"tag":128,"props":814,"children":815},{"style":693},[816],{"type":29,"value":600},{"type":23,"tag":128,"props":818,"children":819},{"style":155},[820],{"type":29,"value":652},{"type":23,"tag":128,"props":822,"children":823},{"style":693},[824],{"type":29,"value":545},{"type":23,"tag":128,"props":826,"children":827},{"style":155},[828],{"type":29,"value":829},");\n",{"type":23,"tag":128,"props":831,"children":833},{"class":130,"line":832},17,[834,838,842,846,850,854,858,862,867,871,875,879,883],{"type":23,"tag":128,"props":835,"children":836},{"style":134},[837],{"type":29,"value":564},{"type":23,"tag":128,"props":839,"children":840},{"style":176},[841],{"type":29,"value":781},{"type":23,"tag":128,"props":843,"children":844},{"style":215},[845],{"type":29,"value":283},{"type":23,"tag":128,"props":847,"children":848},{"style":155},[849],{"type":29,"value":790},{"type":23,"tag":128,"props":851,"children":852},{"style":134},[853],{"type":29,"value":578},{"type":23,"tag":128,"props":855,"children":856},{"style":693},[857],{"type":29,"value":494},{"type":23,"tag":128,"props":859,"children":860},{"style":155},[861],{"type":29,"value":803},{"type":23,"tag":128,"props":863,"children":864},{"style":215},[865],{"type":29,"value":866},"RemoveAt",{"type":23,"tag":128,"props":868,"children":869},{"style":155},[870],{"type":29,"value":637},{"type":23,"tag":128,"props":872,"children":873},{"style":693},[874],{"type":29,"value":600},{"type":23,"tag":128,"props":876,"children":877},{"style":155},[878],{"type":29,"value":652},{"type":23,"tag":128,"props":880,"children":881},{"style":693},[882],{"type":29,"value":545},{"type":23,"tag":128,"props":884,"children":885},{"style":155},[886],{"type":29,"value":829},{"type":23,"tag":128,"props":888,"children":890},{"class":130,"line":889},18,[891],{"type":23,"tag":128,"props":892,"children":893},{"style":155},[894],{"type":29,"value":375},{"type":23,"tag":31,"props":896,"children":897},{},[898,904,906,912,914,919],{"type":23,"tag":58,"props":899,"children":901},{"className":900},[],[902],{"type":29,"value":903},"Execute",{"type":29,"value":905},"と",{"type":23,"tag":58,"props":907,"children":909},{"className":908},[],[910],{"type":29,"value":911},"Undo",{"type":29,"value":913},"が対称になっているのがCommandパターンの気持ちよさです。",{"type":23,"tag":58,"props":915,"children":917},{"className":916},[],[918],{"type":29,"value":911},{"type":29,"value":920},"で失敗するような操作は原則として作らない、というのが運用上の鉄則です。",{"type":23,"tag":24,"props":922,"children":924},{"id":923},"undoredomanager2本のstackで履歴を管理",[925],{"type":29,"value":926},"UndoRedoManager：2本のStackで履歴を管理",{"type":23,"tag":31,"props":928,"children":929},{},[930,932,937,939,945,946,952],{"type":29,"value":931},"履歴の管理本体は",{"type":23,"tag":58,"props":933,"children":935},{"className":934},[],[936],{"type":29,"value":71},{"type":29,"value":938},"です。",{"type":23,"tag":58,"props":940,"children":942},{"className":941},[],[943],{"type":29,"value":944},"_undoStack",{"type":29,"value":905},{"type":23,"tag":58,"props":947,"children":949},{"className":948},[],[950],{"type":29,"value":951},"_redoStack",{"type":29,"value":953},"の2本で押したり引いたりします。",{"type":23,"tag":118,"props":955,"children":957},{"className":120,"code":956,"language":122,"meta":7,"style":7},"public class UndoRedoManager\n{\n    private readonly Stack\u003CICommand> _undoStack = new();\n    private readonly Stack\u003CICommand> _redoStack = new();\n    private readonly int _maxHistorySize;\n\n    public event Action? StateChanged;\n\n    public UndoRedoManager(int maxHistorySize = 100)\n    {\n        _maxHistorySize = maxHistorySize;\n    }\n\n    public bool CanUndo => _undoStack.Count > 0;\n    public bool CanRedo => _redoStack.Count > 0;\n\n    public void Execute(ICommand command)\n    {\n        command.Execute();\n        _undoStack.Push(command);\n        _redoStack.Clear();\n        TrimHistory();\n        StateChanged?.Invoke();\n    }\n\n    public void Undo()\n    {\n        if (!CanUndo) return;\n        var command = _undoStack.Pop();\n        command.Undo();\n        _redoStack.Push(command);\n        StateChanged?.Invoke();\n    }\n\n    public void Redo()\n    {\n        if (!CanRedo) return;\n        var command = _redoStack.Pop();\n        command.Execute();\n        _undoStack.Push(command);\n        StateChanged?.Invoke();\n    }\n}\n",[958],{"type":23,"tag":58,"props":959,"children":960},{"__ignoreMap":7},[961,977,984,1030,1074,1098,1105,1136,1143,1183,1190,1210,1217,1224,1272,1316,1323,1355,1362,1383,1414,1436,1449,1475,1483,1491,1512,1520,1558,1592,1612,1640,1664,1672,1680,1701,1709,1742,1774,1794,1822,1846,1854],{"type":23,"tag":128,"props":962,"children":963},{"class":130,"line":17},[964,968,972],{"type":23,"tag":128,"props":965,"children":966},{"style":134},[967],{"type":29,"value":137},{"type":23,"tag":128,"props":969,"children":970},{"style":134},[971],{"type":29,"value":450},{"type":23,"tag":128,"props":973,"children":974},{"style":145},[975],{"type":29,"value":976}," UndoRedoManager\n",{"type":23,"tag":128,"props":978,"children":979},{"class":130,"line":151},[980],{"type":23,"tag":128,"props":981,"children":982},{"style":155},[983],{"type":29,"value":158},{"type":23,"tag":128,"props":985,"children":986},{"class":130,"line":161},[987,991,995,1000,1004,1008,1012,1017,1021,1026],{"type":23,"tag":128,"props":988,"children":989},{"style":134},[990],{"type":29,"value":479},{"type":23,"tag":128,"props":992,"children":993},{"style":134},[994],{"type":29,"value":484},{"type":23,"tag":128,"props":996,"children":997},{"style":145},[998],{"type":29,"value":999}," Stack",{"type":23,"tag":128,"props":1001,"children":1002},{"style":155},[1003],{"type":29,"value":173},{"type":23,"tag":128,"props":1005,"children":1006},{"style":145},[1007],{"type":29,"value":63},{"type":23,"tag":128,"props":1009,"children":1010},{"style":155},[1011],{"type":29,"value":184},{"type":23,"tag":128,"props":1013,"children":1014},{"style":215},[1015],{"type":29,"value":1016}," _undoStack",{"type":23,"tag":128,"props":1018,"children":1019},{"style":155},[1020],{"type":29,"value":701},{"type":23,"tag":128,"props":1022,"children":1023},{"style":134},[1024],{"type":29,"value":1025}," new",{"type":23,"tag":128,"props":1027,"children":1028},{"style":155},[1029],{"type":29,"value":223},{"type":23,"tag":128,"props":1031,"children":1032},{"class":130,"line":206},[1033,1037,1041,1045,1049,1053,1057,1062,1066,1070],{"type":23,"tag":128,"props":1034,"children":1035},{"style":134},[1036],{"type":29,"value":479},{"type":23,"tag":128,"props":1038,"children":1039},{"style":134},[1040],{"type":29,"value":484},{"type":23,"tag":128,"props":1042,"children":1043},{"style":145},[1044],{"type":29,"value":999},{"type":23,"tag":128,"props":1046,"children":1047},{"style":155},[1048],{"type":29,"value":173},{"type":23,"tag":128,"props":1050,"children":1051},{"style":145},[1052],{"type":29,"value":63},{"type":23,"tag":128,"props":1054,"children":1055},{"style":155},[1056],{"type":29,"value":184},{"type":23,"tag":128,"props":1058,"children":1059},{"style":215},[1060],{"type":29,"value":1061}," _redoStack",{"type":23,"tag":128,"props":1063,"children":1064},{"style":155},[1065],{"type":29,"value":701},{"type":23,"tag":128,"props":1067,"children":1068},{"style":134},[1069],{"type":29,"value":1025},{"type":23,"tag":128,"props":1071,"children":1072},{"style":155},[1073],{"type":29,"value":223},{"type":23,"tag":128,"props":1075,"children":1076},{"class":130,"line":226},[1077,1081,1085,1089,1094],{"type":23,"tag":128,"props":1078,"children":1079},{"style":134},[1080],{"type":29,"value":479},{"type":23,"tag":128,"props":1082,"children":1083},{"style":134},[1084],{"type":29,"value":484},{"type":23,"tag":128,"props":1086,"children":1087},{"style":176},[1088],{"type":29,"value":515},{"type":23,"tag":128,"props":1090,"children":1091},{"style":215},[1092],{"type":29,"value":1093}," _maxHistorySize",{"type":23,"tag":128,"props":1095,"children":1096},{"style":155},[1097],{"type":29,"value":499},{"type":23,"tag":128,"props":1099,"children":1100},{"class":130,"line":236},[1101],{"type":23,"tag":128,"props":1102,"children":1103},{"emptyLinePlaceholder":230},[1104],{"type":29,"value":233},{"type":23,"tag":128,"props":1106,"children":1107},{"class":130,"line":273},[1108,1112,1117,1122,1127,1132],{"type":23,"tag":128,"props":1109,"children":1110},{"style":134},[1111],{"type":29,"value":564},{"type":23,"tag":128,"props":1113,"children":1114},{"style":134},[1115],{"type":29,"value":1116}," event",{"type":23,"tag":128,"props":1118,"children":1119},{"style":145},[1120],{"type":29,"value":1121}," Action",{"type":23,"tag":128,"props":1123,"children":1124},{"style":155},[1125],{"type":29,"value":1126},"?",{"type":23,"tag":128,"props":1128,"children":1129},{"style":215},[1130],{"type":29,"value":1131}," StateChanged",{"type":23,"tag":128,"props":1133,"children":1134},{"style":155},[1135],{"type":29,"value":499},{"type":23,"tag":128,"props":1137,"children":1138},{"class":130,"line":290},[1139],{"type":23,"tag":128,"props":1140,"children":1141},{"emptyLinePlaceholder":230},[1142],{"type":29,"value":233},{"type":23,"tag":128,"props":1144,"children":1145},{"class":130,"line":298},[1146,1150,1155,1159,1164,1169,1173,1179],{"type":23,"tag":128,"props":1147,"children":1148},{"style":134},[1149],{"type":29,"value":564},{"type":23,"tag":128,"props":1151,"children":1152},{"style":215},[1153],{"type":29,"value":1154}," UndoRedoManager",{"type":23,"tag":128,"props":1156,"children":1157},{"style":155},[1158],{"type":29,"value":637},{"type":23,"tag":128,"props":1160,"children":1161},{"style":176},[1162],{"type":29,"value":1163},"int",{"type":23,"tag":128,"props":1165,"children":1166},{"style":215},[1167],{"type":29,"value":1168}," maxHistorySize",{"type":23,"tag":128,"props":1170,"children":1171},{"style":155},[1172],{"type":29,"value":701},{"type":23,"tag":128,"props":1174,"children":1176},{"style":1175},"--shiki-default:#4C9A91",[1177],{"type":29,"value":1178}," 100",{"type":23,"tag":128,"props":1180,"children":1181},{"style":155},[1182],{"type":29,"value":679},{"type":23,"tag":128,"props":1184,"children":1185},{"class":130,"line":335},[1186],{"type":23,"tag":128,"props":1187,"children":1188},{"style":155},[1189],{"type":29,"value":687},{"type":23,"tag":128,"props":1191,"children":1192},{"class":130,"line":369},[1193,1198,1202,1206],{"type":23,"tag":128,"props":1194,"children":1195},{"style":693},[1196],{"type":29,"value":1197},"        _maxHistorySize",{"type":23,"tag":128,"props":1199,"children":1200},{"style":155},[1201],{"type":29,"value":701},{"type":23,"tag":128,"props":1203,"children":1204},{"style":693},[1205],{"type":29,"value":1168},{"type":23,"tag":128,"props":1207,"children":1208},{"style":155},[1209],{"type":29,"value":499},{"type":23,"tag":128,"props":1211,"children":1212},{"class":130,"line":712},[1213],{"type":23,"tag":128,"props":1214,"children":1215},{"style":155},[1216],{"type":29,"value":760},{"type":23,"tag":128,"props":1218,"children":1219},{"class":130,"line":733},[1220],{"type":23,"tag":128,"props":1221,"children":1222},{"emptyLinePlaceholder":230},[1223],{"type":29,"value":233},{"type":23,"tag":128,"props":1225,"children":1226},{"class":130,"line":754},[1227,1231,1236,1241,1245,1249,1253,1258,1263,1268],{"type":23,"tag":128,"props":1228,"children":1229},{"style":134},[1230],{"type":29,"value":564},{"type":23,"tag":128,"props":1232,"children":1233},{"style":176},[1234],{"type":29,"value":1235}," bool",{"type":23,"tag":128,"props":1237,"children":1238},{"style":215},[1239],{"type":29,"value":1240}," CanUndo",{"type":23,"tag":128,"props":1242,"children":1243},{"style":134},[1244],{"type":29,"value":578},{"type":23,"tag":128,"props":1246,"children":1247},{"style":693},[1248],{"type":29,"value":1016},{"type":23,"tag":128,"props":1250,"children":1251},{"style":155},[1252],{"type":29,"value":803},{"type":23,"tag":128,"props":1254,"children":1255},{"style":693},[1256],{"type":29,"value":1257},"Count",{"type":23,"tag":128,"props":1259,"children":1260},{"style":155},[1261],{"type":29,"value":1262}," >",{"type":23,"tag":128,"props":1264,"children":1265},{"style":1175},[1266],{"type":29,"value":1267}," 0",{"type":23,"tag":128,"props":1269,"children":1270},{"style":155},[1271],{"type":29,"value":499},{"type":23,"tag":128,"props":1273,"children":1274},{"class":130,"line":763},[1275,1279,1283,1288,1292,1296,1300,1304,1308,1312],{"type":23,"tag":128,"props":1276,"children":1277},{"style":134},[1278],{"type":29,"value":564},{"type":23,"tag":128,"props":1280,"children":1281},{"style":176},[1282],{"type":29,"value":1235},{"type":23,"tag":128,"props":1284,"children":1285},{"style":215},[1286],{"type":29,"value":1287}," CanRedo",{"type":23,"tag":128,"props":1289,"children":1290},{"style":134},[1291],{"type":29,"value":578},{"type":23,"tag":128,"props":1293,"children":1294},{"style":693},[1295],{"type":29,"value":1061},{"type":23,"tag":128,"props":1297,"children":1298},{"style":155},[1299],{"type":29,"value":803},{"type":23,"tag":128,"props":1301,"children":1302},{"style":693},[1303],{"type":29,"value":1257},{"type":23,"tag":128,"props":1305,"children":1306},{"style":155},[1307],{"type":29,"value":1262},{"type":23,"tag":128,"props":1309,"children":1310},{"style":1175},[1311],{"type":29,"value":1267},{"type":23,"tag":128,"props":1313,"children":1314},{"style":155},[1315],{"type":29,"value":499},{"type":23,"tag":128,"props":1317,"children":1318},{"class":130,"line":771},[1319],{"type":23,"tag":128,"props":1320,"children":1321},{"emptyLinePlaceholder":230},[1322],{"type":29,"value":233},{"type":23,"tag":128,"props":1324,"children":1325},{"class":130,"line":832},[1326,1330,1334,1338,1342,1346,1351],{"type":23,"tag":128,"props":1327,"children":1328},{"style":134},[1329],{"type":29,"value":564},{"type":23,"tag":128,"props":1331,"children":1332},{"style":176},[1333],{"type":29,"value":781},{"type":23,"tag":128,"props":1335,"children":1336},{"style":215},[1337],{"type":29,"value":218},{"type":23,"tag":128,"props":1339,"children":1340},{"style":155},[1341],{"type":29,"value":637},{"type":23,"tag":128,"props":1343,"children":1344},{"style":145},[1345],{"type":29,"value":63},{"type":23,"tag":128,"props":1347,"children":1348},{"style":215},[1349],{"type":29,"value":1350}," command",{"type":23,"tag":128,"props":1352,"children":1353},{"style":155},[1354],{"type":29,"value":679},{"type":23,"tag":128,"props":1356,"children":1357},{"class":130,"line":889},[1358],{"type":23,"tag":128,"props":1359,"children":1360},{"style":155},[1361],{"type":29,"value":687},{"type":23,"tag":128,"props":1363,"children":1365},{"class":130,"line":1364},19,[1366,1371,1375,1379],{"type":23,"tag":128,"props":1367,"children":1368},{"style":693},[1369],{"type":29,"value":1370},"        command",{"type":23,"tag":128,"props":1372,"children":1373},{"style":155},[1374],{"type":29,"value":803},{"type":23,"tag":128,"props":1376,"children":1377},{"style":215},[1378],{"type":29,"value":903},{"type":23,"tag":128,"props":1380,"children":1381},{"style":155},[1382],{"type":29,"value":223},{"type":23,"tag":128,"props":1384,"children":1386},{"class":130,"line":1385},20,[1387,1392,1396,1401,1405,1410],{"type":23,"tag":128,"props":1388,"children":1389},{"style":693},[1390],{"type":29,"value":1391},"        _undoStack",{"type":23,"tag":128,"props":1393,"children":1394},{"style":155},[1395],{"type":29,"value":803},{"type":23,"tag":128,"props":1397,"children":1398},{"style":215},[1399],{"type":29,"value":1400},"Push",{"type":23,"tag":128,"props":1402,"children":1403},{"style":155},[1404],{"type":29,"value":637},{"type":23,"tag":128,"props":1406,"children":1407},{"style":693},[1408],{"type":29,"value":1409},"command",{"type":23,"tag":128,"props":1411,"children":1412},{"style":155},[1413],{"type":29,"value":829},{"type":23,"tag":128,"props":1415,"children":1417},{"class":130,"line":1416},21,[1418,1423,1427,1432],{"type":23,"tag":128,"props":1419,"children":1420},{"style":693},[1421],{"type":29,"value":1422},"        _redoStack",{"type":23,"tag":128,"props":1424,"children":1425},{"style":155},[1426],{"type":29,"value":803},{"type":23,"tag":128,"props":1428,"children":1429},{"style":215},[1430],{"type":29,"value":1431},"Clear",{"type":23,"tag":128,"props":1433,"children":1434},{"style":155},[1435],{"type":29,"value":223},{"type":23,"tag":128,"props":1437,"children":1439},{"class":130,"line":1438},22,[1440,1445],{"type":23,"tag":128,"props":1441,"children":1442},{"style":215},[1443],{"type":29,"value":1444},"        TrimHistory",{"type":23,"tag":128,"props":1446,"children":1447},{"style":155},[1448],{"type":29,"value":223},{"type":23,"tag":128,"props":1450,"children":1452},{"class":130,"line":1451},23,[1453,1458,1462,1466,1471],{"type":23,"tag":128,"props":1454,"children":1455},{"style":693},[1456],{"type":29,"value":1457},"        StateChanged",{"type":23,"tag":128,"props":1459,"children":1460},{"style":134},[1461],{"type":29,"value":1126},{"type":23,"tag":128,"props":1463,"children":1464},{"style":155},[1465],{"type":29,"value":803},{"type":23,"tag":128,"props":1467,"children":1468},{"style":215},[1469],{"type":29,"value":1470},"Invoke",{"type":23,"tag":128,"props":1472,"children":1473},{"style":155},[1474],{"type":29,"value":223},{"type":23,"tag":128,"props":1476,"children":1478},{"class":130,"line":1477},24,[1479],{"type":23,"tag":128,"props":1480,"children":1481},{"style":155},[1482],{"type":29,"value":760},{"type":23,"tag":128,"props":1484,"children":1486},{"class":130,"line":1485},25,[1487],{"type":23,"tag":128,"props":1488,"children":1489},{"emptyLinePlaceholder":230},[1490],{"type":29,"value":233},{"type":23,"tag":128,"props":1492,"children":1494},{"class":130,"line":1493},26,[1495,1499,1503,1507],{"type":23,"tag":128,"props":1496,"children":1497},{"style":134},[1498],{"type":29,"value":564},{"type":23,"tag":128,"props":1500,"children":1501},{"style":176},[1502],{"type":29,"value":781},{"type":23,"tag":128,"props":1504,"children":1505},{"style":215},[1506],{"type":29,"value":283},{"type":23,"tag":128,"props":1508,"children":1509},{"style":155},[1510],{"type":29,"value":1511},"()\n",{"type":23,"tag":128,"props":1513,"children":1515},{"class":130,"line":1514},27,[1516],{"type":23,"tag":128,"props":1517,"children":1518},{"style":155},[1519],{"type":29,"value":687},{"type":23,"tag":128,"props":1521,"children":1523},{"class":130,"line":1522},28,[1524,1529,1534,1539,1544,1549,1554],{"type":23,"tag":128,"props":1525,"children":1526},{"style":176},[1527],{"type":29,"value":1528},"        if",{"type":23,"tag":128,"props":1530,"children":1531},{"style":155},[1532],{"type":29,"value":1533}," (",{"type":23,"tag":128,"props":1535,"children":1536},{"style":134},[1537],{"type":29,"value":1538},"!",{"type":23,"tag":128,"props":1540,"children":1541},{"style":693},[1542],{"type":29,"value":1543},"CanUndo",{"type":23,"tag":128,"props":1545,"children":1546},{"style":155},[1547],{"type":29,"value":1548},")",{"type":23,"tag":128,"props":1550,"children":1551},{"style":176},[1552],{"type":29,"value":1553}," return",{"type":23,"tag":128,"props":1555,"children":1556},{"style":155},[1557],{"type":29,"value":499},{"type":23,"tag":128,"props":1559,"children":1561},{"class":130,"line":1560},29,[1562,1567,1571,1575,1579,1583,1588],{"type":23,"tag":128,"props":1563,"children":1564},{"style":134},[1565],{"type":29,"value":1566},"        var",{"type":23,"tag":128,"props":1568,"children":1569},{"style":215},[1570],{"type":29,"value":1350},{"type":23,"tag":128,"props":1572,"children":1573},{"style":155},[1574],{"type":29,"value":701},{"type":23,"tag":128,"props":1576,"children":1577},{"style":693},[1578],{"type":29,"value":1016},{"type":23,"tag":128,"props":1580,"children":1581},{"style":155},[1582],{"type":29,"value":803},{"type":23,"tag":128,"props":1584,"children":1585},{"style":215},[1586],{"type":29,"value":1587},"Pop",{"type":23,"tag":128,"props":1589,"children":1590},{"style":155},[1591],{"type":29,"value":223},{"type":23,"tag":128,"props":1593,"children":1595},{"class":130,"line":1594},30,[1596,1600,1604,1608],{"type":23,"tag":128,"props":1597,"children":1598},{"style":693},[1599],{"type":29,"value":1370},{"type":23,"tag":128,"props":1601,"children":1602},{"style":155},[1603],{"type":29,"value":803},{"type":23,"tag":128,"props":1605,"children":1606},{"style":215},[1607],{"type":29,"value":911},{"type":23,"tag":128,"props":1609,"children":1610},{"style":155},[1611],{"type":29,"value":223},{"type":23,"tag":128,"props":1613,"children":1615},{"class":130,"line":1614},31,[1616,1620,1624,1628,1632,1636],{"type":23,"tag":128,"props":1617,"children":1618},{"style":693},[1619],{"type":29,"value":1422},{"type":23,"tag":128,"props":1621,"children":1622},{"style":155},[1623],{"type":29,"value":803},{"type":23,"tag":128,"props":1625,"children":1626},{"style":215},[1627],{"type":29,"value":1400},{"type":23,"tag":128,"props":1629,"children":1630},{"style":155},[1631],{"type":29,"value":637},{"type":23,"tag":128,"props":1633,"children":1634},{"style":693},[1635],{"type":29,"value":1409},{"type":23,"tag":128,"props":1637,"children":1638},{"style":155},[1639],{"type":29,"value":829},{"type":23,"tag":128,"props":1641,"children":1643},{"class":130,"line":1642},32,[1644,1648,1652,1656,1660],{"type":23,"tag":128,"props":1645,"children":1646},{"style":693},[1647],{"type":29,"value":1457},{"type":23,"tag":128,"props":1649,"children":1650},{"style":134},[1651],{"type":29,"value":1126},{"type":23,"tag":128,"props":1653,"children":1654},{"style":155},[1655],{"type":29,"value":803},{"type":23,"tag":128,"props":1657,"children":1658},{"style":215},[1659],{"type":29,"value":1470},{"type":23,"tag":128,"props":1661,"children":1662},{"style":155},[1663],{"type":29,"value":223},{"type":23,"tag":128,"props":1665,"children":1667},{"class":130,"line":1666},33,[1668],{"type":23,"tag":128,"props":1669,"children":1670},{"style":155},[1671],{"type":29,"value":760},{"type":23,"tag":128,"props":1673,"children":1675},{"class":130,"line":1674},34,[1676],{"type":23,"tag":128,"props":1677,"children":1678},{"emptyLinePlaceholder":230},[1679],{"type":29,"value":233},{"type":23,"tag":128,"props":1681,"children":1683},{"class":130,"line":1682},35,[1684,1688,1692,1697],{"type":23,"tag":128,"props":1685,"children":1686},{"style":134},[1687],{"type":29,"value":564},{"type":23,"tag":128,"props":1689,"children":1690},{"style":176},[1691],{"type":29,"value":781},{"type":23,"tag":128,"props":1693,"children":1694},{"style":215},[1695],{"type":29,"value":1696}," Redo",{"type":23,"tag":128,"props":1698,"children":1699},{"style":155},[1700],{"type":29,"value":1511},{"type":23,"tag":128,"props":1702,"children":1704},{"class":130,"line":1703},36,[1705],{"type":23,"tag":128,"props":1706,"children":1707},{"style":155},[1708],{"type":29,"value":687},{"type":23,"tag":128,"props":1710,"children":1712},{"class":130,"line":1711},37,[1713,1717,1721,1725,1730,1734,1738],{"type":23,"tag":128,"props":1714,"children":1715},{"style":176},[1716],{"type":29,"value":1528},{"type":23,"tag":128,"props":1718,"children":1719},{"style":155},[1720],{"type":29,"value":1533},{"type":23,"tag":128,"props":1722,"children":1723},{"style":134},[1724],{"type":29,"value":1538},{"type":23,"tag":128,"props":1726,"children":1727},{"style":693},[1728],{"type":29,"value":1729},"CanRedo",{"type":23,"tag":128,"props":1731,"children":1732},{"style":155},[1733],{"type":29,"value":1548},{"type":23,"tag":128,"props":1735,"children":1736},{"style":176},[1737],{"type":29,"value":1553},{"type":23,"tag":128,"props":1739,"children":1740},{"style":155},[1741],{"type":29,"value":499},{"type":23,"tag":128,"props":1743,"children":1745},{"class":130,"line":1744},38,[1746,1750,1754,1758,1762,1766,1770],{"type":23,"tag":128,"props":1747,"children":1748},{"style":134},[1749],{"type":29,"value":1566},{"type":23,"tag":128,"props":1751,"children":1752},{"style":215},[1753],{"type":29,"value":1350},{"type":23,"tag":128,"props":1755,"children":1756},{"style":155},[1757],{"type":29,"value":701},{"type":23,"tag":128,"props":1759,"children":1760},{"style":693},[1761],{"type":29,"value":1061},{"type":23,"tag":128,"props":1763,"children":1764},{"style":155},[1765],{"type":29,"value":803},{"type":23,"tag":128,"props":1767,"children":1768},{"style":215},[1769],{"type":29,"value":1587},{"type":23,"tag":128,"props":1771,"children":1772},{"style":155},[1773],{"type":29,"value":223},{"type":23,"tag":128,"props":1775,"children":1777},{"class":130,"line":1776},39,[1778,1782,1786,1790],{"type":23,"tag":128,"props":1779,"children":1780},{"style":693},[1781],{"type":29,"value":1370},{"type":23,"tag":128,"props":1783,"children":1784},{"style":155},[1785],{"type":29,"value":803},{"type":23,"tag":128,"props":1787,"children":1788},{"style":215},[1789],{"type":29,"value":903},{"type":23,"tag":128,"props":1791,"children":1792},{"style":155},[1793],{"type":29,"value":223},{"type":23,"tag":128,"props":1795,"children":1797},{"class":130,"line":1796},40,[1798,1802,1806,1810,1814,1818],{"type":23,"tag":128,"props":1799,"children":1800},{"style":693},[1801],{"type":29,"value":1391},{"type":23,"tag":128,"props":1803,"children":1804},{"style":155},[1805],{"type":29,"value":803},{"type":23,"tag":128,"props":1807,"children":1808},{"style":215},[1809],{"type":29,"value":1400},{"type":23,"tag":128,"props":1811,"children":1812},{"style":155},[1813],{"type":29,"value":637},{"type":23,"tag":128,"props":1815,"children":1816},{"style":693},[1817],{"type":29,"value":1409},{"type":23,"tag":128,"props":1819,"children":1820},{"style":155},[1821],{"type":29,"value":829},{"type":23,"tag":128,"props":1823,"children":1825},{"class":130,"line":1824},41,[1826,1830,1834,1838,1842],{"type":23,"tag":128,"props":1827,"children":1828},{"style":693},[1829],{"type":29,"value":1457},{"type":23,"tag":128,"props":1831,"children":1832},{"style":134},[1833],{"type":29,"value":1126},{"type":23,"tag":128,"props":1835,"children":1836},{"style":155},[1837],{"type":29,"value":803},{"type":23,"tag":128,"props":1839,"children":1840},{"style":215},[1841],{"type":29,"value":1470},{"type":23,"tag":128,"props":1843,"children":1844},{"style":155},[1845],{"type":29,"value":223},{"type":23,"tag":128,"props":1847,"children":1849},{"class":130,"line":1848},42,[1850],{"type":23,"tag":128,"props":1851,"children":1852},{"style":155},[1853],{"type":29,"value":760},{"type":23,"tag":128,"props":1855,"children":1857},{"class":130,"line":1856},43,[1858],{"type":23,"tag":128,"props":1859,"children":1860},{"style":155},[1861],{"type":29,"value":375},{"type":23,"tag":31,"props":1863,"children":1864},{},[1865],{"type":29,"value":1866},"ここで注目ポイントが2つあります。",{"type":23,"tag":31,"props":1868,"children":1869},{},[1870,1872,1877,1879,1885],{"type":29,"value":1871},"1つ目は",{"type":23,"tag":58,"props":1873,"children":1875},{"className":1874},[],[1876],{"type":29,"value":903},{"type":29,"value":1878},"の中で",{"type":23,"tag":58,"props":1880,"children":1882},{"className":1881},[],[1883],{"type":29,"value":1884},"_redoStack.Clear()",{"type":29,"value":1886},"を呼んでいる点です。Undoした後に新しい操作を実行したら、「未来の枝」（Redo可能だった履歴）は全部捨てるのが一般的な挙動です。Adobe系のアプリでUndo→別の操作をするとRedoが効かなくなるのと同じ挙動で、ユーザーが違和感なく使えます。",{"type":23,"tag":31,"props":1888,"children":1889},{},[1890,1892,1898,1900,1906,1908,1913,1915,1920],{"type":29,"value":1891},"2つ目は",{"type":23,"tag":58,"props":1893,"children":1895},{"className":1894},[],[1896],{"type":29,"value":1897},"StateChanged",{"type":29,"value":1899},"イベントです。Blazor側ではこのイベントに購読して、UndoボタンとRedoボタンの",{"type":23,"tag":58,"props":1901,"children":1903},{"className":1902},[],[1904],{"type":29,"value":1905},"disabled",{"type":29,"value":1907},"属性を",{"type":23,"tag":58,"props":1909,"children":1911},{"className":1910},[],[1912],{"type":29,"value":1543},{"type":29,"value":1914}," / ",{"type":23,"tag":58,"props":1916,"children":1918},{"className":1917},[],[1919],{"type":29,"value":1729},{"type":29,"value":1921},"に連動させます。イベント駆動にしておけば、どこからコマンドを実行してもUIが自動で追従します。",{"type":23,"tag":24,"props":1923,"children":1925},{"id":1924},"trimhistory履歴上限でメモリリークを防ぐ",[1926],{"type":29,"value":1927},"TrimHistory：履歴上限でメモリリークを防ぐ",{"type":23,"tag":31,"props":1929,"children":1930},{},[1931],{"type":29,"value":1932},"長時間編集していると履歴はどんどん積み上がります。PICOMの楽譜エディタでは、音符の配置・削除・移動だけでも1時間の編集で数百件を超えるので、履歴を無制限に持っているとメモリが膨れ上がる恐れがあります。",{"type":23,"tag":31,"props":1934,"children":1935},{},[1936,1938,1944],{"type":29,"value":1937},"そこで、最大履歴数を超えたら古いものから捨てる",{"type":23,"tag":58,"props":1939,"children":1941},{"className":1940},[],[1942],{"type":29,"value":1943},"TrimHistory",{"type":29,"value":1945},"を実装しました。",{"type":23,"tag":118,"props":1947,"children":1949},{"className":120,"code":1948,"language":122,"meta":7,"style":7},"private void TrimHistory()\n{\n    while (_undoStack.Count > _maxHistorySize)\n    {\n        var list = _undoStack.ToList();\n        list.RemoveAt(list.Count - 1);\n        _undoStack.Clear();\n        foreach (var item in list.AsEnumerable().Reverse())\n        {\n            _undoStack.Push(item);\n        }\n    }\n}\n",[1950],{"type":23,"tag":58,"props":1951,"children":1952},{"__ignoreMap":7},[1953,1974,1981,2017,2024,2057,2104,2123,2178,2186,2215,2223,2230],{"type":23,"tag":128,"props":1954,"children":1955},{"class":130,"line":17},[1956,1961,1965,1970],{"type":23,"tag":128,"props":1957,"children":1958},{"style":134},[1959],{"type":29,"value":1960},"private",{"type":23,"tag":128,"props":1962,"children":1963},{"style":176},[1964],{"type":29,"value":781},{"type":23,"tag":128,"props":1966,"children":1967},{"style":215},[1968],{"type":29,"value":1969}," TrimHistory",{"type":23,"tag":128,"props":1971,"children":1972},{"style":155},[1973],{"type":29,"value":1511},{"type":23,"tag":128,"props":1975,"children":1976},{"class":130,"line":151},[1977],{"type":23,"tag":128,"props":1978,"children":1979},{"style":155},[1980],{"type":29,"value":158},{"type":23,"tag":128,"props":1982,"children":1983},{"class":130,"line":161},[1984,1989,1993,1997,2001,2005,2009,2013],{"type":23,"tag":128,"props":1985,"children":1986},{"style":176},[1987],{"type":29,"value":1988},"    while",{"type":23,"tag":128,"props":1990,"children":1991},{"style":155},[1992],{"type":29,"value":1533},{"type":23,"tag":128,"props":1994,"children":1995},{"style":693},[1996],{"type":29,"value":944},{"type":23,"tag":128,"props":1998,"children":1999},{"style":155},[2000],{"type":29,"value":803},{"type":23,"tag":128,"props":2002,"children":2003},{"style":693},[2004],{"type":29,"value":1257},{"type":23,"tag":128,"props":2006,"children":2007},{"style":155},[2008],{"type":29,"value":1262},{"type":23,"tag":128,"props":2010,"children":2011},{"style":693},[2012],{"type":29,"value":1093},{"type":23,"tag":128,"props":2014,"children":2015},{"style":155},[2016],{"type":29,"value":679},{"type":23,"tag":128,"props":2018,"children":2019},{"class":130,"line":206},[2020],{"type":23,"tag":128,"props":2021,"children":2022},{"style":155},[2023],{"type":29,"value":687},{"type":23,"tag":128,"props":2025,"children":2026},{"class":130,"line":226},[2027,2031,2036,2040,2044,2048,2053],{"type":23,"tag":128,"props":2028,"children":2029},{"style":134},[2030],{"type":29,"value":1566},{"type":23,"tag":128,"props":2032,"children":2033},{"style":215},[2034],{"type":29,"value":2035}," list",{"type":23,"tag":128,"props":2037,"children":2038},{"style":155},[2039],{"type":29,"value":701},{"type":23,"tag":128,"props":2041,"children":2042},{"style":693},[2043],{"type":29,"value":1016},{"type":23,"tag":128,"props":2045,"children":2046},{"style":155},[2047],{"type":29,"value":803},{"type":23,"tag":128,"props":2049,"children":2050},{"style":215},[2051],{"type":29,"value":2052},"ToList",{"type":23,"tag":128,"props":2054,"children":2055},{"style":155},[2056],{"type":29,"value":223},{"type":23,"tag":128,"props":2058,"children":2059},{"class":130,"line":236},[2060,2065,2069,2073,2077,2082,2086,2090,2095,2100],{"type":23,"tag":128,"props":2061,"children":2062},{"style":693},[2063],{"type":29,"value":2064},"        list",{"type":23,"tag":128,"props":2066,"children":2067},{"style":155},[2068],{"type":29,"value":803},{"type":23,"tag":128,"props":2070,"children":2071},{"style":215},[2072],{"type":29,"value":866},{"type":23,"tag":128,"props":2074,"children":2075},{"style":155},[2076],{"type":29,"value":637},{"type":23,"tag":128,"props":2078,"children":2079},{"style":693},[2080],{"type":29,"value":2081},"list",{"type":23,"tag":128,"props":2083,"children":2084},{"style":155},[2085],{"type":29,"value":803},{"type":23,"tag":128,"props":2087,"children":2088},{"style":693},[2089],{"type":29,"value":1257},{"type":23,"tag":128,"props":2091,"children":2092},{"style":134},[2093],{"type":29,"value":2094}," -",{"type":23,"tag":128,"props":2096,"children":2097},{"style":1175},[2098],{"type":29,"value":2099}," 1",{"type":23,"tag":128,"props":2101,"children":2102},{"style":155},[2103],{"type":29,"value":829},{"type":23,"tag":128,"props":2105,"children":2106},{"class":130,"line":273},[2107,2111,2115,2119],{"type":23,"tag":128,"props":2108,"children":2109},{"style":693},[2110],{"type":29,"value":1391},{"type":23,"tag":128,"props":2112,"children":2113},{"style":155},[2114],{"type":29,"value":803},{"type":23,"tag":128,"props":2116,"children":2117},{"style":215},[2118],{"type":29,"value":1431},{"type":23,"tag":128,"props":2120,"children":2121},{"style":155},[2122],{"type":29,"value":223},{"type":23,"tag":128,"props":2124,"children":2125},{"class":130,"line":290},[2126,2131,2135,2140,2145,2150,2154,2158,2163,2168,2173],{"type":23,"tag":128,"props":2127,"children":2128},{"style":176},[2129],{"type":29,"value":2130},"        foreach",{"type":23,"tag":128,"props":2132,"children":2133},{"style":155},[2134],{"type":29,"value":1533},{"type":23,"tag":128,"props":2136,"children":2137},{"style":134},[2138],{"type":29,"value":2139},"var",{"type":23,"tag":128,"props":2141,"children":2142},{"style":215},[2143],{"type":29,"value":2144}," item",{"type":23,"tag":128,"props":2146,"children":2147},{"style":176},[2148],{"type":29,"value":2149}," in",{"type":23,"tag":128,"props":2151,"children":2152},{"style":693},[2153],{"type":29,"value":2035},{"type":23,"tag":128,"props":2155,"children":2156},{"style":155},[2157],{"type":29,"value":803},{"type":23,"tag":128,"props":2159,"children":2160},{"style":215},[2161],{"type":29,"value":2162},"AsEnumerable",{"type":23,"tag":128,"props":2164,"children":2165},{"style":155},[2166],{"type":29,"value":2167},"().",{"type":23,"tag":128,"props":2169,"children":2170},{"style":215},[2171],{"type":29,"value":2172},"Reverse",{"type":23,"tag":128,"props":2174,"children":2175},{"style":155},[2176],{"type":29,"value":2177},"())\n",{"type":23,"tag":128,"props":2179,"children":2180},{"class":130,"line":298},[2181],{"type":23,"tag":128,"props":2182,"children":2183},{"style":155},[2184],{"type":29,"value":2185},"        {\n",{"type":23,"tag":128,"props":2187,"children":2188},{"class":130,"line":335},[2189,2194,2198,2202,2206,2211],{"type":23,"tag":128,"props":2190,"children":2191},{"style":693},[2192],{"type":29,"value":2193},"            _undoStack",{"type":23,"tag":128,"props":2195,"children":2196},{"style":155},[2197],{"type":29,"value":803},{"type":23,"tag":128,"props":2199,"children":2200},{"style":215},[2201],{"type":29,"value":1400},{"type":23,"tag":128,"props":2203,"children":2204},{"style":155},[2205],{"type":29,"value":637},{"type":23,"tag":128,"props":2207,"children":2208},{"style":693},[2209],{"type":29,"value":2210},"item",{"type":23,"tag":128,"props":2212,"children":2213},{"style":155},[2214],{"type":29,"value":829},{"type":23,"tag":128,"props":2216,"children":2217},{"class":130,"line":369},[2218],{"type":23,"tag":128,"props":2219,"children":2220},{"style":155},[2221],{"type":29,"value":2222},"        }\n",{"type":23,"tag":128,"props":2224,"children":2225},{"class":130,"line":712},[2226],{"type":23,"tag":128,"props":2227,"children":2228},{"style":155},[2229],{"type":29,"value":760},{"type":23,"tag":128,"props":2231,"children":2232},{"class":130,"line":733},[2233],{"type":23,"tag":128,"props":2234,"children":2235},{"style":155},[2236],{"type":29,"value":375},{"type":23,"tag":31,"props":2238,"children":2239},{},[2240,2246,2248,2254,2256,2262],{"type":23,"tag":58,"props":2241,"children":2243},{"className":2242},[],[2244],{"type":29,"value":2245},"Stack\u003CT>",{"type":29,"value":2247},"は最古要素を直接削除するAPIを持っていないので、一度",{"type":23,"tag":58,"props":2249,"children":2251},{"className":2250},[],[2252],{"type":29,"value":2253},"List",{"type":29,"value":2255},"に展開して最後（最古）を削り、再度Pushし直しています。愚直ですが確実で、",{"type":23,"tag":58,"props":2257,"children":2259},{"className":2258},[],[2260],{"type":29,"value":2261},"_maxHistorySize = 100",{"type":29,"value":2263},"程度なら実行コストは無視できる範囲です。",{"type":23,"tag":2265,"props":2266,"children":2269},"tradeoff-box",{"cons-label":2267,"pros-label":2268},"デメリット","メリット",[2270,2313],{"type":23,"tag":2271,"props":2272,"children":2273},"template",{"v-slot:pros":7},[2274],{"type":23,"tag":2275,"props":2276,"children":2277},"ul",{},[2278,2292,2308],{"type":23,"tag":2279,"props":2280,"children":2281},"li",{},[2282,2284,2290],{"type":29,"value":2283},"Undo/Redoの挙動が",{"type":23,"tag":58,"props":2285,"children":2287},{"className":2286},[],[2288],{"type":29,"value":2289},"Push/Pop",{"type":29,"value":2291},"と綺麗に一致して読みやすい",{"type":23,"tag":2279,"props":2293,"children":2294},{},[2295,2300,2301,2306],{"type":23,"tag":58,"props":2296,"children":2298},{"className":2297},[],[2299],{"type":29,"value":1431},{"type":29,"value":1914},{"type":23,"tag":58,"props":2302,"children":2304},{"className":2303},[],[2305],{"type":29,"value":1257},{"type":29,"value":2307},"などの基本APIで十分足りる",{"type":23,"tag":2279,"props":2309,"children":2310},{},[2311],{"type":29,"value":2312},"履歴構造の意図が型から自明",{"type":23,"tag":2271,"props":2314,"children":2315},{"v-slot:cons":7},[2316,2329],{"type":23,"tag":2279,"props":2317,"children":2318},{},[2319,2321,2327],{"type":29,"value":2320},"最古要素の削除に展開→詰め直しが必要（",{"type":23,"tag":58,"props":2322,"children":2324},{"className":2323},[],[2325],{"type":29,"value":2326},"LinkedList",{"type":29,"value":2328},"ならO(1)）",{"type":23,"tag":2279,"props":2330,"children":2331},{},[2332],{"type":29,"value":2333},"双方向からの参照が欲しくなった時に不便",{"type":23,"tag":31,"props":2335,"children":2336},{},[2337,2339,2345,2347,2353],{"type":29,"value":2338},"もし履歴数が数万を想定する場合は",{"type":23,"tag":58,"props":2340,"children":2342},{"className":2341},[],[2343],{"type":29,"value":2344},"LinkedList\u003CICommand>",{"type":29,"value":2346},"や",{"type":23,"tag":58,"props":2348,"children":2350},{"className":2349},[],[2351],{"type":29,"value":2352},"Deque",{"type":29,"value":2354},"的な構造を使った方が良いのですが、エディタのUndo履歴は100件前後で十分なので、可読性を優先してStackにしています。",{"type":23,"tag":24,"props":2356,"children":2358},{"id":2357},"compositecommand複数操作を1手にまとめる",[2359],{"type":29,"value":2360},"CompositeCommand：複数操作を1手にまとめる",{"type":23,"tag":31,"props":2362,"children":2363},{},[2364,2366,2371,2373,2378,2380,2386],{"type":29,"value":2365},"実装していて一番「これがないとまずい」と感じたのが",{"type":23,"tag":58,"props":2367,"children":2369},{"className":2368},[],[2370],{"type":29,"value":79},{"type":29,"value":2372},"です。たとえばピアノロール上で",{"type":23,"tag":99,"props":2374,"children":2375},{},[2376],{"type":29,"value":2377},"複数の音符を選択して一括で下に動かす",{"type":29,"value":2379},"操作を考えると、これを1つ1つ",{"type":23,"tag":58,"props":2381,"children":2383},{"className":2382},[],[2384],{"type":29,"value":2385},"MoveNotesCommand",{"type":29,"value":2387},"として履歴に積んだら、Undoを何回も押さないと元の状態に戻りません。",{"type":23,"tag":31,"props":2389,"children":2390},{},[2391],{"type":29,"value":2392},"解決策は「中に複数のコマンドを持ち、Execute/Undoで全部を順番に回すコマンド」を作ることです。",{"type":23,"tag":118,"props":2394,"children":2396},{"className":120,"code":2395,"language":122,"meta":7,"style":7},"public class CompositeCommand : ICommand\n{\n    private readonly List\u003CICommand> _commands;\n    private readonly string _description;\n\n    public string Description => _description;\n\n    public CompositeCommand(string description, IEnumerable\u003CICommand> commands)\n    {\n        _description = description;\n        _commands = commands.ToList();\n    }\n\n    public void Execute()\n    {\n        foreach (var command in _commands)\n        {\n            command.Execute();\n        }\n    }\n\n    public void Undo()\n    {\n        for (int i = _commands.Count - 1; i >= 0; i--)\n        {\n            _commands[i].Undo();\n        }\n    }\n}\n",[2397],{"type":23,"tag":58,"props":2398,"children":2399},{"__ignoreMap":7},[2400,2424,2431,2468,2492,2499,2526,2533,2588,2595,2615,2643,2650,2657,2676,2683,2714,2721,2741,2748,2755,2762,2781,2788,2867,2874,2905,2912,2919],{"type":23,"tag":128,"props":2401,"children":2402},{"class":130,"line":17},[2403,2407,2411,2416,2420],{"type":23,"tag":128,"props":2404,"children":2405},{"style":134},[2406],{"type":29,"value":137},{"type":23,"tag":128,"props":2408,"children":2409},{"style":134},[2410],{"type":29,"value":450},{"type":23,"tag":128,"props":2412,"children":2413},{"style":145},[2414],{"type":29,"value":2415}," CompositeCommand",{"type":23,"tag":128,"props":2417,"children":2418},{"style":155},[2419],{"type":29,"value":460},{"type":23,"tag":128,"props":2421,"children":2422},{"style":145},[2423],{"type":29,"value":148},{"type":23,"tag":128,"props":2425,"children":2426},{"class":130,"line":151},[2427],{"type":23,"tag":128,"props":2428,"children":2429},{"style":155},[2430],{"type":29,"value":158},{"type":23,"tag":128,"props":2432,"children":2433},{"class":130,"line":161},[2434,2438,2442,2447,2451,2455,2459,2464],{"type":23,"tag":128,"props":2435,"children":2436},{"style":134},[2437],{"type":29,"value":479},{"type":23,"tag":128,"props":2439,"children":2440},{"style":134},[2441],{"type":29,"value":484},{"type":23,"tag":128,"props":2443,"children":2444},{"style":145},[2445],{"type":29,"value":2446}," List",{"type":23,"tag":128,"props":2448,"children":2449},{"style":155},[2450],{"type":29,"value":173},{"type":23,"tag":128,"props":2452,"children":2453},{"style":145},[2454],{"type":29,"value":63},{"type":23,"tag":128,"props":2456,"children":2457},{"style":155},[2458],{"type":29,"value":184},{"type":23,"tag":128,"props":2460,"children":2461},{"style":215},[2462],{"type":29,"value":2463}," _commands",{"type":23,"tag":128,"props":2465,"children":2466},{"style":155},[2467],{"type":29,"value":499},{"type":23,"tag":128,"props":2469,"children":2470},{"class":130,"line":206},[2471,2475,2479,2483,2488],{"type":23,"tag":128,"props":2472,"children":2473},{"style":134},[2474],{"type":29,"value":479},{"type":23,"tag":128,"props":2476,"children":2477},{"style":134},[2478],{"type":29,"value":484},{"type":23,"tag":128,"props":2480,"children":2481},{"style":176},[2482],{"type":29,"value":569},{"type":23,"tag":128,"props":2484,"children":2485},{"style":215},[2486],{"type":29,"value":2487}," _description",{"type":23,"tag":128,"props":2489,"children":2490},{"style":155},[2491],{"type":29,"value":499},{"type":23,"tag":128,"props":2493,"children":2494},{"class":130,"line":226},[2495],{"type":23,"tag":128,"props":2496,"children":2497},{"emptyLinePlaceholder":230},[2498],{"type":29,"value":233},{"type":23,"tag":128,"props":2500,"children":2501},{"class":130,"line":236},[2502,2506,2510,2514,2518,2522],{"type":23,"tag":128,"props":2503,"children":2504},{"style":134},[2505],{"type":29,"value":564},{"type":23,"tag":128,"props":2507,"children":2508},{"style":176},[2509],{"type":29,"value":569},{"type":23,"tag":128,"props":2511,"children":2512},{"style":215},[2513],{"type":29,"value":346},{"type":23,"tag":128,"props":2515,"children":2516},{"style":134},[2517],{"type":29,"value":578},{"type":23,"tag":128,"props":2519,"children":2520},{"style":693},[2521],{"type":29,"value":2487},{"type":23,"tag":128,"props":2523,"children":2524},{"style":155},[2525],{"type":29,"value":499},{"type":23,"tag":128,"props":2527,"children":2528},{"class":130,"line":273},[2529],{"type":23,"tag":128,"props":2530,"children":2531},{"emptyLinePlaceholder":230},[2532],{"type":29,"value":233},{"type":23,"tag":128,"props":2534,"children":2535},{"class":130,"line":290},[2536,2540,2544,2548,2553,2558,2562,2567,2571,2575,2579,2584],{"type":23,"tag":128,"props":2537,"children":2538},{"style":134},[2539],{"type":29,"value":564},{"type":23,"tag":128,"props":2541,"children":2542},{"style":215},[2543],{"type":29,"value":2415},{"type":23,"tag":128,"props":2545,"children":2546},{"style":155},[2547],{"type":29,"value":637},{"type":23,"tag":128,"props":2549,"children":2550},{"style":176},[2551],{"type":29,"value":2552},"string",{"type":23,"tag":128,"props":2554,"children":2555},{"style":215},[2556],{"type":29,"value":2557}," description",{"type":23,"tag":128,"props":2559,"children":2560},{"style":155},[2561],{"type":29,"value":652},{"type":23,"tag":128,"props":2563,"children":2564},{"style":145},[2565],{"type":29,"value":2566}," IEnumerable",{"type":23,"tag":128,"props":2568,"children":2569},{"style":155},[2570],{"type":29,"value":173},{"type":23,"tag":128,"props":2572,"children":2573},{"style":145},[2574],{"type":29,"value":63},{"type":23,"tag":128,"props":2576,"children":2577},{"style":155},[2578],{"type":29,"value":184},{"type":23,"tag":128,"props":2580,"children":2581},{"style":215},[2582],{"type":29,"value":2583}," commands",{"type":23,"tag":128,"props":2585,"children":2586},{"style":155},[2587],{"type":29,"value":679},{"type":23,"tag":128,"props":2589,"children":2590},{"class":130,"line":298},[2591],{"type":23,"tag":128,"props":2592,"children":2593},{"style":155},[2594],{"type":29,"value":687},{"type":23,"tag":128,"props":2596,"children":2597},{"class":130,"line":335},[2598,2603,2607,2611],{"type":23,"tag":128,"props":2599,"children":2600},{"style":693},[2601],{"type":29,"value":2602},"        _description",{"type":23,"tag":128,"props":2604,"children":2605},{"style":155},[2606],{"type":29,"value":701},{"type":23,"tag":128,"props":2608,"children":2609},{"style":693},[2610],{"type":29,"value":2557},{"type":23,"tag":128,"props":2612,"children":2613},{"style":155},[2614],{"type":29,"value":499},{"type":23,"tag":128,"props":2616,"children":2617},{"class":130,"line":369},[2618,2623,2627,2631,2635,2639],{"type":23,"tag":128,"props":2619,"children":2620},{"style":693},[2621],{"type":29,"value":2622},"        _commands",{"type":23,"tag":128,"props":2624,"children":2625},{"style":155},[2626],{"type":29,"value":701},{"type":23,"tag":128,"props":2628,"children":2629},{"style":693},[2630],{"type":29,"value":2583},{"type":23,"tag":128,"props":2632,"children":2633},{"style":155},[2634],{"type":29,"value":803},{"type":23,"tag":128,"props":2636,"children":2637},{"style":215},[2638],{"type":29,"value":2052},{"type":23,"tag":128,"props":2640,"children":2641},{"style":155},[2642],{"type":29,"value":223},{"type":23,"tag":128,"props":2644,"children":2645},{"class":130,"line":712},[2646],{"type":23,"tag":128,"props":2647,"children":2648},{"style":155},[2649],{"type":29,"value":760},{"type":23,"tag":128,"props":2651,"children":2652},{"class":130,"line":733},[2653],{"type":23,"tag":128,"props":2654,"children":2655},{"emptyLinePlaceholder":230},[2656],{"type":29,"value":233},{"type":23,"tag":128,"props":2658,"children":2659},{"class":130,"line":754},[2660,2664,2668,2672],{"type":23,"tag":128,"props":2661,"children":2662},{"style":134},[2663],{"type":29,"value":564},{"type":23,"tag":128,"props":2665,"children":2666},{"style":176},[2667],{"type":29,"value":781},{"type":23,"tag":128,"props":2669,"children":2670},{"style":215},[2671],{"type":29,"value":218},{"type":23,"tag":128,"props":2673,"children":2674},{"style":155},[2675],{"type":29,"value":1511},{"type":23,"tag":128,"props":2677,"children":2678},{"class":130,"line":763},[2679],{"type":23,"tag":128,"props":2680,"children":2681},{"style":155},[2682],{"type":29,"value":687},{"type":23,"tag":128,"props":2684,"children":2685},{"class":130,"line":771},[2686,2690,2694,2698,2702,2706,2710],{"type":23,"tag":128,"props":2687,"children":2688},{"style":176},[2689],{"type":29,"value":2130},{"type":23,"tag":128,"props":2691,"children":2692},{"style":155},[2693],{"type":29,"value":1533},{"type":23,"tag":128,"props":2695,"children":2696},{"style":134},[2697],{"type":29,"value":2139},{"type":23,"tag":128,"props":2699,"children":2700},{"style":215},[2701],{"type":29,"value":1350},{"type":23,"tag":128,"props":2703,"children":2704},{"style":176},[2705],{"type":29,"value":2149},{"type":23,"tag":128,"props":2707,"children":2708},{"style":693},[2709],{"type":29,"value":2463},{"type":23,"tag":128,"props":2711,"children":2712},{"style":155},[2713],{"type":29,"value":679},{"type":23,"tag":128,"props":2715,"children":2716},{"class":130,"line":832},[2717],{"type":23,"tag":128,"props":2718,"children":2719},{"style":155},[2720],{"type":29,"value":2185},{"type":23,"tag":128,"props":2722,"children":2723},{"class":130,"line":889},[2724,2729,2733,2737],{"type":23,"tag":128,"props":2725,"children":2726},{"style":693},[2727],{"type":29,"value":2728},"            command",{"type":23,"tag":128,"props":2730,"children":2731},{"style":155},[2732],{"type":29,"value":803},{"type":23,"tag":128,"props":2734,"children":2735},{"style":215},[2736],{"type":29,"value":903},{"type":23,"tag":128,"props":2738,"children":2739},{"style":155},[2740],{"type":29,"value":223},{"type":23,"tag":128,"props":2742,"children":2743},{"class":130,"line":1364},[2744],{"type":23,"tag":128,"props":2745,"children":2746},{"style":155},[2747],{"type":29,"value":2222},{"type":23,"tag":128,"props":2749,"children":2750},{"class":130,"line":1385},[2751],{"type":23,"tag":128,"props":2752,"children":2753},{"style":155},[2754],{"type":29,"value":760},{"type":23,"tag":128,"props":2756,"children":2757},{"class":130,"line":1416},[2758],{"type":23,"tag":128,"props":2759,"children":2760},{"emptyLinePlaceholder":230},[2761],{"type":29,"value":233},{"type":23,"tag":128,"props":2763,"children":2764},{"class":130,"line":1438},[2765,2769,2773,2777],{"type":23,"tag":128,"props":2766,"children":2767},{"style":134},[2768],{"type":29,"value":564},{"type":23,"tag":128,"props":2770,"children":2771},{"style":176},[2772],{"type":29,"value":781},{"type":23,"tag":128,"props":2774,"children":2775},{"style":215},[2776],{"type":29,"value":283},{"type":23,"tag":128,"props":2778,"children":2779},{"style":155},[2780],{"type":29,"value":1511},{"type":23,"tag":128,"props":2782,"children":2783},{"class":130,"line":1451},[2784],{"type":23,"tag":128,"props":2785,"children":2786},{"style":155},[2787],{"type":29,"value":687},{"type":23,"tag":128,"props":2789,"children":2790},{"class":130,"line":1477},[2791,2796,2800,2804,2809,2813,2817,2821,2825,2829,2833,2837,2841,2846,2850,2854,2858,2863],{"type":23,"tag":128,"props":2792,"children":2793},{"style":176},[2794],{"type":29,"value":2795},"        for",{"type":23,"tag":128,"props":2797,"children":2798},{"style":155},[2799],{"type":29,"value":1533},{"type":23,"tag":128,"props":2801,"children":2802},{"style":176},[2803],{"type":29,"value":1163},{"type":23,"tag":128,"props":2805,"children":2806},{"style":215},[2807],{"type":29,"value":2808}," i",{"type":23,"tag":128,"props":2810,"children":2811},{"style":155},[2812],{"type":29,"value":701},{"type":23,"tag":128,"props":2814,"children":2815},{"style":693},[2816],{"type":29,"value":2463},{"type":23,"tag":128,"props":2818,"children":2819},{"style":155},[2820],{"type":29,"value":803},{"type":23,"tag":128,"props":2822,"children":2823},{"style":693},[2824],{"type":29,"value":1257},{"type":23,"tag":128,"props":2826,"children":2827},{"style":134},[2828],{"type":29,"value":2094},{"type":23,"tag":128,"props":2830,"children":2831},{"style":1175},[2832],{"type":29,"value":2099},{"type":23,"tag":128,"props":2834,"children":2835},{"style":155},[2836],{"type":29,"value":361},{"type":23,"tag":128,"props":2838,"children":2839},{"style":693},[2840],{"type":29,"value":2808},{"type":23,"tag":128,"props":2842,"children":2843},{"style":155},[2844],{"type":29,"value":2845}," >=",{"type":23,"tag":128,"props":2847,"children":2848},{"style":1175},[2849],{"type":29,"value":1267},{"type":23,"tag":128,"props":2851,"children":2852},{"style":155},[2853],{"type":29,"value":361},{"type":23,"tag":128,"props":2855,"children":2856},{"style":693},[2857],{"type":29,"value":2808},{"type":23,"tag":128,"props":2859,"children":2860},{"style":134},[2861],{"type":29,"value":2862},"--",{"type":23,"tag":128,"props":2864,"children":2865},{"style":155},[2866],{"type":29,"value":679},{"type":23,"tag":128,"props":2868,"children":2869},{"class":130,"line":1485},[2870],{"type":23,"tag":128,"props":2871,"children":2872},{"style":155},[2873],{"type":29,"value":2185},{"type":23,"tag":128,"props":2875,"children":2876},{"class":130,"line":1493},[2877,2882,2887,2892,2897,2901],{"type":23,"tag":128,"props":2878,"children":2879},{"style":693},[2880],{"type":29,"value":2881},"            _commands",{"type":23,"tag":128,"props":2883,"children":2884},{"style":155},[2885],{"type":29,"value":2886},"[",{"type":23,"tag":128,"props":2888,"children":2889},{"style":693},[2890],{"type":29,"value":2891},"i",{"type":23,"tag":128,"props":2893,"children":2894},{"style":155},[2895],{"type":29,"value":2896},"].",{"type":23,"tag":128,"props":2898,"children":2899},{"style":215},[2900],{"type":29,"value":911},{"type":23,"tag":128,"props":2902,"children":2903},{"style":155},[2904],{"type":29,"value":223},{"type":23,"tag":128,"props":2906,"children":2907},{"class":130,"line":1514},[2908],{"type":23,"tag":128,"props":2909,"children":2910},{"style":155},[2911],{"type":29,"value":2222},{"type":23,"tag":128,"props":2913,"children":2914},{"class":130,"line":1522},[2915],{"type":23,"tag":128,"props":2916,"children":2917},{"style":155},[2918],{"type":29,"value":760},{"type":23,"tag":128,"props":2920,"children":2921},{"class":130,"line":1560},[2922],{"type":23,"tag":128,"props":2923,"children":2924},{"style":155},[2925],{"type":29,"value":375},{"type":23,"tag":31,"props":2927,"children":2928},{},[2929,2931,2936],{"type":29,"value":2930},"地味に大事なのが",{"type":23,"tag":99,"props":2932,"children":2933},{},[2934],{"type":29,"value":2935},"Undo時は逆順で回す",{"type":29,"value":2937},"ところです。「AをしてからBをした」なら、戻す時は「Bを戻してからAを戻す」のが正しい順序です。コマンド間に副作用の依存があるとこの順序を間違えた時にバグるので、注意ポイントです。",{"type":23,"tag":31,"props":2939,"children":2940},{},[2941],{"type":29,"value":2942},"使い方はこんな感じです。",{"type":23,"tag":118,"props":2944,"children":2946},{"className":120,"code":2945,"language":122,"meta":7,"style":7},"var commands = selectedNotes.Select(note => new MoveNoteCommand(track, note, delta));\nvar composite = new CompositeCommand($\"{selectedNotes.Count}個の音符を移動\", commands);\n_undoRedoManager.Execute(composite);\n",[2947],{"type":23,"tag":58,"props":2948,"children":2949},{"__ignoreMap":7},[2950,3033,3108],{"type":23,"tag":128,"props":2951,"children":2952},{"class":130,"line":17},[2953,2957,2961,2965,2970,2974,2979,2983,2988,2992,2996,3001,3005,3010,3014,3019,3023,3028],{"type":23,"tag":128,"props":2954,"children":2955},{"style":134},[2956],{"type":29,"value":2139},{"type":23,"tag":128,"props":2958,"children":2959},{"style":215},[2960],{"type":29,"value":2583},{"type":23,"tag":128,"props":2962,"children":2963},{"style":155},[2964],{"type":29,"value":701},{"type":23,"tag":128,"props":2966,"children":2967},{"style":693},[2968],{"type":29,"value":2969}," selectedNotes",{"type":23,"tag":128,"props":2971,"children":2972},{"style":155},[2973],{"type":29,"value":803},{"type":23,"tag":128,"props":2975,"children":2976},{"style":215},[2977],{"type":29,"value":2978},"Select",{"type":23,"tag":128,"props":2980,"children":2981},{"style":155},[2982],{"type":29,"value":637},{"type":23,"tag":128,"props":2984,"children":2985},{"style":215},[2986],{"type":29,"value":2987},"note",{"type":23,"tag":128,"props":2989,"children":2990},{"style":134},[2991],{"type":29,"value":578},{"type":23,"tag":128,"props":2993,"children":2994},{"style":134},[2995],{"type":29,"value":1025},{"type":23,"tag":128,"props":2997,"children":2998},{"style":145},[2999],{"type":29,"value":3000}," MoveNoteCommand",{"type":23,"tag":128,"props":3002,"children":3003},{"style":155},[3004],{"type":29,"value":637},{"type":23,"tag":128,"props":3006,"children":3007},{"style":693},[3008],{"type":29,"value":3009},"track",{"type":23,"tag":128,"props":3011,"children":3012},{"style":155},[3013],{"type":29,"value":652},{"type":23,"tag":128,"props":3015,"children":3016},{"style":693},[3017],{"type":29,"value":3018}," note",{"type":23,"tag":128,"props":3020,"children":3021},{"style":155},[3022],{"type":29,"value":652},{"type":23,"tag":128,"props":3024,"children":3025},{"style":693},[3026],{"type":29,"value":3027}," delta",{"type":23,"tag":128,"props":3029,"children":3030},{"style":155},[3031],{"type":29,"value":3032},"));\n",{"type":23,"tag":128,"props":3034,"children":3035},{"class":130,"line":151},[3036,3040,3045,3049,3053,3057,3061,3066,3070,3075,3079,3083,3087,3092,3096,3100,3104],{"type":23,"tag":128,"props":3037,"children":3038},{"style":134},[3039],{"type":29,"value":2139},{"type":23,"tag":128,"props":3041,"children":3042},{"style":215},[3043],{"type":29,"value":3044}," composite",{"type":23,"tag":128,"props":3046,"children":3047},{"style":155},[3048],{"type":29,"value":701},{"type":23,"tag":128,"props":3050,"children":3051},{"style":134},[3052],{"type":29,"value":1025},{"type":23,"tag":128,"props":3054,"children":3055},{"style":145},[3056],{"type":29,"value":2415},{"type":23,"tag":128,"props":3058,"children":3059},{"style":155},[3060],{"type":29,"value":637},{"type":23,"tag":128,"props":3062,"children":3063},{"style":581},[3064],{"type":29,"value":3065},"$\"",{"type":23,"tag":128,"props":3067,"children":3068},{"style":155},[3069],{"type":29,"value":595},{"type":23,"tag":128,"props":3071,"children":3072},{"style":587},[3073],{"type":29,"value":3074},"selectedNotes",{"type":23,"tag":128,"props":3076,"children":3077},{"style":155},[3078],{"type":29,"value":803},{"type":23,"tag":128,"props":3080,"children":3081},{"style":587},[3082],{"type":29,"value":1257},{"type":23,"tag":128,"props":3084,"children":3085},{"style":155},[3086],{"type":29,"value":605},{"type":23,"tag":128,"props":3088,"children":3089},{"style":587},[3090],{"type":29,"value":3091},"個の音符を移動",{"type":23,"tag":128,"props":3093,"children":3094},{"style":581},[3095],{"type":29,"value":610},{"type":23,"tag":128,"props":3097,"children":3098},{"style":155},[3099],{"type":29,"value":652},{"type":23,"tag":128,"props":3101,"children":3102},{"style":693},[3103],{"type":29,"value":2583},{"type":23,"tag":128,"props":3105,"children":3106},{"style":155},[3107],{"type":29,"value":829},{"type":23,"tag":128,"props":3109,"children":3110},{"class":130,"line":161},[3111,3116,3120,3124,3128,3133],{"type":23,"tag":128,"props":3112,"children":3113},{"style":693},[3114],{"type":29,"value":3115},"_undoRedoManager",{"type":23,"tag":128,"props":3117,"children":3118},{"style":155},[3119],{"type":29,"value":803},{"type":23,"tag":128,"props":3121,"children":3122},{"style":215},[3123],{"type":29,"value":903},{"type":23,"tag":128,"props":3125,"children":3126},{"style":155},[3127],{"type":29,"value":637},{"type":23,"tag":128,"props":3129,"children":3130},{"style":693},[3131],{"type":29,"value":3132},"composite",{"type":23,"tag":128,"props":3134,"children":3135},{"style":155},[3136],{"type":29,"value":829},{"type":23,"tag":31,"props":3138,"children":3139},{},[3140],{"type":29,"value":3141},"ユーザー視点では「選択した音符を全部動かす」を1手としてUndoできるので、快適に使えます。",{"type":23,"tag":24,"props":3143,"children":3145},{"id":3144},"まとめ",[3146],{"type":29,"value":3144},{"type":23,"tag":2275,"props":3148,"children":3149},{},[3150,3179,3189,3194],{"type":23,"tag":2279,"props":3151,"children":3152},{},[3153,3158,3160,3165,3166,3171,3172,3177],{"type":23,"tag":58,"props":3154,"children":3156},{"className":3155},[],[3157],{"type":29,"value":63},{"type":29,"value":3159},"は",{"type":23,"tag":58,"props":3161,"children":3163},{"className":3162},[],[3164],{"type":29,"value":903},{"type":29,"value":1914},{"type":23,"tag":58,"props":3167,"children":3169},{"className":3168},[],[3170],{"type":29,"value":911},{"type":29,"value":1914},{"type":23,"tag":58,"props":3173,"children":3175},{"className":3174},[],[3176],{"type":29,"value":386},{"type":29,"value":3178},"の3点セットで十分",{"type":23,"tag":2279,"props":3180,"children":3181},{},[3182,3187],{"type":23,"tag":58,"props":3183,"children":3185},{"className":3184},[],[3186],{"type":29,"value":71},{"type":29,"value":3188},"は2本のStackで管理、Redoスタックは新規Executeで破棄",{"type":23,"tag":2279,"props":3190,"children":3191},{},[3192],{"type":29,"value":3193},"履歴上限を入れないとメモリ使用量が膨らむことに注意",{"type":23,"tag":2279,"props":3195,"children":3196},{},[3197,3199,3204],{"type":29,"value":3198},"複数操作を一括でする",{"type":23,"tag":58,"props":3200,"children":3202},{"className":3201},[],[3203],{"type":29,"value":79},{"type":29,"value":3205},"は必須レベルで便利。Undo時は逆順に回す",{"type":23,"tag":3207,"props":3208,"children":3209},"style",{},[3210],{"type":29,"value":3211},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":151,"depth":151,"links":3213},[3214,3215,3216,3217,3218,3219,3220],{"id":26,"depth":151,"text":26},{"id":84,"depth":151,"text":87},{"id":108,"depth":151,"text":111},{"id":923,"depth":151,"text":926},{"id":1924,"depth":151,"text":1927},{"id":2357,"depth":151,"text":2360},{"id":3144,"depth":151,"text":3144},"markdown","content:articles:tech:blazor:undo-redo-command-pattern.md","content","articles/tech/blazor/undo-redo-command-pattern.md","articles/tech/blazor/undo-redo-command-pattern","md",1776356306042]