[{"data":1,"prerenderedAt":2743},["ShallowReactive",2],{"content-query-hvk3n4a0mY":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":2737,"_id":2738,"_source":2739,"_file":2740,"_stem":2741,"_extension":2742},"/articles/tech/blazor/diff-detectable-object","blazor",false,"","ObservableObjectとDiffDetectableObjectで「変更された？」を自動判定する","Blazor WebAssemblyで「保存ボタンを押すべきか」を自動判定する仕組みを、ObservableObjectとDiffDetectableObjectという2つのシンプルなクラスで実装しました。MVVM ToolkitのObservableObjectとの違いや、イベント駆動で状態フラグを同期させる設計のコツを解説します。","2026-04-14",[12,13,14,15,16],"C#","MVVM","Blazor","INotifyPropertyChanged","PICOM",1,{"loc":4,"lastmod":10,"priority":17},{"type":20,"children":21,"toc":2725},"root",[22,30,60,90,118,124,143,694,705,873,880,893,905,967,973,992,1421,1477,1496,1615,1620,1630,1974,1993,1999,2004,2278,2314,2468,2474,2486,2644,2654,2676,2681,2719],{"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,36,42,44,51,53,58],{"type":29,"value":35},"楽譜エディタを作っていて、地味に困るのが「",{"type":23,"tag":37,"props":38,"children":39},"strong",{},[40],{"type":29,"value":41},"未保存の変更があるかどうか",{"type":29,"value":43},"」の判定です。タイトルバーに",{"type":23,"tag":45,"props":46,"children":48},"code",{"className":47},[],[49],{"type":29,"value":50},"*",{"type":29,"value":52},"を出したり、保存ボタンの活性を切り替えたり、タブを閉じようとしたら「未保存の変更があります」と警告したい。これをやるには、現在のデータが",{"type":23,"tag":37,"props":54,"children":55},{},[56],{"type":29,"value":57},"初期状態から変更されているか",{"type":29,"value":59},"を常に知っておく必要があります。",{"type":23,"tag":31,"props":61,"children":62},{},[63,65,71,73,79,81,88],{"type":29,"value":64},"PICOMでは、この判定を",{"type":23,"tag":45,"props":66,"children":68},{"className":67},[],[69],{"type":29,"value":70},"ObservableObject",{"type":29,"value":72},"と",{"type":23,"tag":45,"props":74,"children":76},{"className":75},[],[77],{"type":29,"value":78},"DiffDetectableObject\u003CT>",{"type":29,"value":80},"という2つの小さなクラスで実装しました。この記事では、その設計と、",{"type":23,"tag":82,"props":83,"children":85},"tooltip",{"content":84},"Microsoftが公開しているMVVM用のユーティリティライブラリ。ObservableObjectやRelayCommandなどを提供する",[86],{"type":29,"value":87},"MVVM Community Toolkit",{"type":29,"value":89},"との違いについて書きます。",{"type":23,"tag":91,"props":92,"children":93},"summary-box",{},[94],{"type":23,"tag":31,"props":95,"children":96},{},[97,102,104,109,111,116],{"type":23,"tag":45,"props":98,"children":100},{"className":99},[],[101],{"type":29,"value":15},{"type":29,"value":103},"を実装した",{"type":23,"tag":45,"props":105,"children":107},{"className":106},[],[108],{"type":29,"value":70},{"type":29,"value":110},"をベースに、初期スナップショットと現在のオブジェクトを持つ",{"type":23,"tag":45,"props":112,"children":114},{"className":113},[],[115],{"type":29,"value":78},{"type":29,"value":117},"を被せることで、差分の有無をイベントで通知できるようにしました。保存ボタンの活性判定や、変更通知バッジの表示など、UI側は状態フラグをそのままバインドするだけで済みます。",{"type":23,"tag":24,"props":119,"children":121},{"id":120},"observableobject最小限のプロパティ変更通知",[122],{"type":29,"value":123},"ObservableObject：最小限のプロパティ変更通知",{"type":23,"tag":31,"props":125,"children":126},{},[127,129,134,136,141],{"type":29,"value":128},"まずは下敷きとなる",{"type":23,"tag":45,"props":130,"children":132},{"className":131},[],[133],{"type":29,"value":70},{"type":29,"value":135},"です。中身は",{"type":23,"tag":45,"props":137,"children":139},{"className":138},[],[140],{"type":29,"value":15},{"type":29,"value":142},"の実装だけで、めちゃくちゃ小さいです。",{"type":23,"tag":144,"props":145,"children":149},"pre",{"className":146,"code":147,"language":148,"meta":7,"style":7},"language-csharp shiki shiki-themes vitesse-dark","using System.ComponentModel;\nusing System.Runtime.CompilerServices;\n\nnamespace Sample;\n\npublic abstract class ObservableObject : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler? PropertyChanged;\n\n    protected void SetProperty\u003CT>(ref T prop, T value, [CallerMemberName] string? name = null)\n    {\n        if (name is null) throw new Exception();\n        prop = value;\n        NotifyPropertyChanged(name);\n    }\n\n    protected void NotifyPropertyChanged(string name)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));\n    }\n}\n","csharp",[150],{"type":23,"tag":45,"props":151,"children":152},{"__ignoreMap":7},[153,186,220,230,249,257,291,300,334,342,453,462,516,537,560,569,577,611,619,677,685],{"type":23,"tag":154,"props":155,"children":157},"span",{"class":156,"line":17},"line",[158,164,170,176,181],{"type":23,"tag":154,"props":159,"children":161},{"style":160},"--shiki-default:#4D9375",[162],{"type":29,"value":163},"using",{"type":23,"tag":154,"props":165,"children":167},{"style":166},"--shiki-default:#5DA994",[168],{"type":29,"value":169}," System",{"type":23,"tag":154,"props":171,"children":173},{"style":172},"--shiki-default:#666666",[174],{"type":29,"value":175},".",{"type":23,"tag":154,"props":177,"children":178},{"style":166},[179],{"type":29,"value":180},"ComponentModel",{"type":23,"tag":154,"props":182,"children":183},{"style":172},[184],{"type":29,"value":185},";\n",{"type":23,"tag":154,"props":187,"children":189},{"class":156,"line":188},2,[190,194,198,202,207,211,216],{"type":23,"tag":154,"props":191,"children":192},{"style":160},[193],{"type":29,"value":163},{"type":23,"tag":154,"props":195,"children":196},{"style":166},[197],{"type":29,"value":169},{"type":23,"tag":154,"props":199,"children":200},{"style":172},[201],{"type":29,"value":175},{"type":23,"tag":154,"props":203,"children":204},{"style":166},[205],{"type":29,"value":206},"Runtime",{"type":23,"tag":154,"props":208,"children":209},{"style":172},[210],{"type":29,"value":175},{"type":23,"tag":154,"props":212,"children":213},{"style":166},[214],{"type":29,"value":215},"CompilerServices",{"type":23,"tag":154,"props":217,"children":218},{"style":172},[219],{"type":29,"value":185},{"type":23,"tag":154,"props":221,"children":223},{"class":156,"line":222},3,[224],{"type":23,"tag":154,"props":225,"children":227},{"emptyLinePlaceholder":226},true,[228],{"type":29,"value":229},"\n",{"type":23,"tag":154,"props":231,"children":233},{"class":156,"line":232},4,[234,240,245],{"type":23,"tag":154,"props":235,"children":237},{"style":236},"--shiki-default:#CB7676",[238],{"type":29,"value":239},"namespace",{"type":23,"tag":154,"props":241,"children":242},{"style":166},[243],{"type":29,"value":244}," Sample",{"type":23,"tag":154,"props":246,"children":247},{"style":172},[248],{"type":29,"value":185},{"type":23,"tag":154,"props":250,"children":252},{"class":156,"line":251},5,[253],{"type":23,"tag":154,"props":254,"children":255},{"emptyLinePlaceholder":226},[256],{"type":29,"value":229},{"type":23,"tag":154,"props":258,"children":260},{"class":156,"line":259},6,[261,266,271,276,281,286],{"type":23,"tag":154,"props":262,"children":263},{"style":236},[264],{"type":29,"value":265},"public",{"type":23,"tag":154,"props":267,"children":268},{"style":236},[269],{"type":29,"value":270}," abstract",{"type":23,"tag":154,"props":272,"children":273},{"style":236},[274],{"type":29,"value":275}," class",{"type":23,"tag":154,"props":277,"children":278},{"style":166},[279],{"type":29,"value":280}," ObservableObject",{"type":23,"tag":154,"props":282,"children":283},{"style":172},[284],{"type":29,"value":285}," :",{"type":23,"tag":154,"props":287,"children":288},{"style":166},[289],{"type":29,"value":290}," INotifyPropertyChanged\n",{"type":23,"tag":154,"props":292,"children":294},{"class":156,"line":293},7,[295],{"type":23,"tag":154,"props":296,"children":297},{"style":172},[298],{"type":29,"value":299},"{\n",{"type":23,"tag":154,"props":301,"children":303},{"class":156,"line":302},8,[304,309,314,319,324,330],{"type":23,"tag":154,"props":305,"children":306},{"style":236},[307],{"type":29,"value":308},"    public",{"type":23,"tag":154,"props":310,"children":311},{"style":236},[312],{"type":29,"value":313}," event",{"type":23,"tag":154,"props":315,"children":316},{"style":166},[317],{"type":29,"value":318}," PropertyChangedEventHandler",{"type":23,"tag":154,"props":320,"children":321},{"style":172},[322],{"type":29,"value":323},"?",{"type":23,"tag":154,"props":325,"children":327},{"style":326},"--shiki-default:#80A665",[328],{"type":29,"value":329}," PropertyChanged",{"type":23,"tag":154,"props":331,"children":332},{"style":172},[333],{"type":29,"value":185},{"type":23,"tag":154,"props":335,"children":337},{"class":156,"line":336},9,[338],{"type":23,"tag":154,"props":339,"children":340},{"emptyLinePlaceholder":226},[341],{"type":29,"value":229},{"type":23,"tag":154,"props":343,"children":345},{"class":156,"line":344},10,[346,351,356,361,366,371,376,381,386,391,396,400,405,409,414,419,424,429,433,438,443,448],{"type":23,"tag":154,"props":347,"children":348},{"style":236},[349],{"type":29,"value":350},"    protected",{"type":23,"tag":154,"props":352,"children":353},{"style":160},[354],{"type":29,"value":355}," void",{"type":23,"tag":154,"props":357,"children":358},{"style":326},[359],{"type":29,"value":360}," SetProperty",{"type":23,"tag":154,"props":362,"children":363},{"style":172},[364],{"type":29,"value":365},"\u003C",{"type":23,"tag":154,"props":367,"children":368},{"style":166},[369],{"type":29,"value":370},"T",{"type":23,"tag":154,"props":372,"children":373},{"style":172},[374],{"type":29,"value":375},">(",{"type":23,"tag":154,"props":377,"children":378},{"style":236},[379],{"type":29,"value":380},"ref",{"type":23,"tag":154,"props":382,"children":383},{"style":166},[384],{"type":29,"value":385}," T",{"type":23,"tag":154,"props":387,"children":388},{"style":326},[389],{"type":29,"value":390}," prop",{"type":23,"tag":154,"props":392,"children":393},{"style":172},[394],{"type":29,"value":395},",",{"type":23,"tag":154,"props":397,"children":398},{"style":166},[399],{"type":29,"value":385},{"type":23,"tag":154,"props":401,"children":402},{"style":326},[403],{"type":29,"value":404}," value",{"type":23,"tag":154,"props":406,"children":407},{"style":172},[408],{"type":29,"value":395},{"type":23,"tag":154,"props":410,"children":411},{"style":172},[412],{"type":29,"value":413}," [",{"type":23,"tag":154,"props":415,"children":416},{"style":166},[417],{"type":29,"value":418},"CallerMemberName",{"type":23,"tag":154,"props":420,"children":421},{"style":172},[422],{"type":29,"value":423},"]",{"type":23,"tag":154,"props":425,"children":426},{"style":160},[427],{"type":29,"value":428}," string",{"type":23,"tag":154,"props":430,"children":431},{"style":172},[432],{"type":29,"value":323},{"type":23,"tag":154,"props":434,"children":435},{"style":326},[436],{"type":29,"value":437}," name",{"type":23,"tag":154,"props":439,"children":440},{"style":172},[441],{"type":29,"value":442}," =",{"type":23,"tag":154,"props":444,"children":445},{"style":236},[446],{"type":29,"value":447}," null",{"type":23,"tag":154,"props":449,"children":450},{"style":172},[451],{"type":29,"value":452},")\n",{"type":23,"tag":154,"props":454,"children":456},{"class":156,"line":455},11,[457],{"type":23,"tag":154,"props":458,"children":459},{"style":172},[460],{"type":29,"value":461},"    {\n",{"type":23,"tag":154,"props":463,"children":465},{"class":156,"line":464},12,[466,471,476,482,487,491,496,501,506,511],{"type":23,"tag":154,"props":467,"children":468},{"style":160},[469],{"type":29,"value":470},"        if",{"type":23,"tag":154,"props":472,"children":473},{"style":172},[474],{"type":29,"value":475}," (",{"type":23,"tag":154,"props":477,"children":479},{"style":478},"--shiki-default:#BD976A",[480],{"type":29,"value":481},"name",{"type":23,"tag":154,"props":483,"children":484},{"style":236},[485],{"type":29,"value":486}," is",{"type":23,"tag":154,"props":488,"children":489},{"style":236},[490],{"type":29,"value":447},{"type":23,"tag":154,"props":492,"children":493},{"style":172},[494],{"type":29,"value":495},")",{"type":23,"tag":154,"props":497,"children":498},{"style":160},[499],{"type":29,"value":500}," throw",{"type":23,"tag":154,"props":502,"children":503},{"style":236},[504],{"type":29,"value":505}," new",{"type":23,"tag":154,"props":507,"children":508},{"style":166},[509],{"type":29,"value":510}," Exception",{"type":23,"tag":154,"props":512,"children":513},{"style":172},[514],{"type":29,"value":515},"();\n",{"type":23,"tag":154,"props":517,"children":519},{"class":156,"line":518},13,[520,525,529,533],{"type":23,"tag":154,"props":521,"children":522},{"style":478},[523],{"type":29,"value":524},"        prop",{"type":23,"tag":154,"props":526,"children":527},{"style":172},[528],{"type":29,"value":442},{"type":23,"tag":154,"props":530,"children":531},{"style":478},[532],{"type":29,"value":404},{"type":23,"tag":154,"props":534,"children":535},{"style":172},[536],{"type":29,"value":185},{"type":23,"tag":154,"props":538,"children":540},{"class":156,"line":539},14,[541,546,551,555],{"type":23,"tag":154,"props":542,"children":543},{"style":326},[544],{"type":29,"value":545},"        NotifyPropertyChanged",{"type":23,"tag":154,"props":547,"children":548},{"style":172},[549],{"type":29,"value":550},"(",{"type":23,"tag":154,"props":552,"children":553},{"style":478},[554],{"type":29,"value":481},{"type":23,"tag":154,"props":556,"children":557},{"style":172},[558],{"type":29,"value":559},");\n",{"type":23,"tag":154,"props":561,"children":563},{"class":156,"line":562},15,[564],{"type":23,"tag":154,"props":565,"children":566},{"style":172},[567],{"type":29,"value":568},"    }\n",{"type":23,"tag":154,"props":570,"children":572},{"class":156,"line":571},16,[573],{"type":23,"tag":154,"props":574,"children":575},{"emptyLinePlaceholder":226},[576],{"type":29,"value":229},{"type":23,"tag":154,"props":578,"children":580},{"class":156,"line":579},17,[581,585,589,594,598,603,607],{"type":23,"tag":154,"props":582,"children":583},{"style":236},[584],{"type":29,"value":350},{"type":23,"tag":154,"props":586,"children":587},{"style":160},[588],{"type":29,"value":355},{"type":23,"tag":154,"props":590,"children":591},{"style":326},[592],{"type":29,"value":593}," NotifyPropertyChanged",{"type":23,"tag":154,"props":595,"children":596},{"style":172},[597],{"type":29,"value":550},{"type":23,"tag":154,"props":599,"children":600},{"style":160},[601],{"type":29,"value":602},"string",{"type":23,"tag":154,"props":604,"children":605},{"style":326},[606],{"type":29,"value":437},{"type":23,"tag":154,"props":608,"children":609},{"style":172},[610],{"type":29,"value":452},{"type":23,"tag":154,"props":612,"children":614},{"class":156,"line":613},18,[615],{"type":23,"tag":154,"props":616,"children":617},{"style":172},[618],{"type":29,"value":461},{"type":23,"tag":154,"props":620,"children":622},{"class":156,"line":621},19,[623,628,632,636,641,645,651,655,659,664,668,672],{"type":23,"tag":154,"props":624,"children":625},{"style":478},[626],{"type":29,"value":627},"        PropertyChanged",{"type":23,"tag":154,"props":629,"children":630},{"style":236},[631],{"type":29,"value":323},{"type":23,"tag":154,"props":633,"children":634},{"style":172},[635],{"type":29,"value":175},{"type":23,"tag":154,"props":637,"children":638},{"style":326},[639],{"type":29,"value":640},"Invoke",{"type":23,"tag":154,"props":642,"children":643},{"style":172},[644],{"type":29,"value":550},{"type":23,"tag":154,"props":646,"children":648},{"style":647},"--shiki-default:#C99076",[649],{"type":29,"value":650},"this",{"type":23,"tag":154,"props":652,"children":653},{"style":172},[654],{"type":29,"value":395},{"type":23,"tag":154,"props":656,"children":657},{"style":236},[658],{"type":29,"value":505},{"type":23,"tag":154,"props":660,"children":661},{"style":166},[662],{"type":29,"value":663}," PropertyChangedEventArgs",{"type":23,"tag":154,"props":665,"children":666},{"style":172},[667],{"type":29,"value":550},{"type":23,"tag":154,"props":669,"children":670},{"style":478},[671],{"type":29,"value":481},{"type":23,"tag":154,"props":673,"children":674},{"style":172},[675],{"type":29,"value":676},"));\n",{"type":23,"tag":154,"props":678,"children":680},{"class":156,"line":679},20,[681],{"type":23,"tag":154,"props":682,"children":683},{"style":172},[684],{"type":29,"value":568},{"type":23,"tag":154,"props":686,"children":688},{"class":156,"line":687},21,[689],{"type":23,"tag":154,"props":690,"children":691},{"style":172},[692],{"type":29,"value":693},"}\n",{"type":23,"tag":31,"props":695,"children":696},{},[697,703],{"type":23,"tag":45,"props":698,"children":700},{"className":699},[],[701],{"type":29,"value":702},"[CallerMemberName]",{"type":29,"value":704},"でプロパティ名を自動取得しているので、派生クラスでは次のように書けます。",{"type":23,"tag":144,"props":706,"children":708},{"className":146,"code":707,"language":148,"meta":7,"style":7},"public class MusicMetadata : ObservableObject\n{\n    private string _title = \"\";\n    public string Title\n    {\n        get => _title;\n        set => SetProperty(ref _title, value);\n    }\n}\n",[709],{"type":23,"tag":45,"props":710,"children":711},{"__ignoreMap":7},[712,737,744,775,791,798,819,859,866],{"type":23,"tag":154,"props":713,"children":714},{"class":156,"line":17},[715,719,723,728,732],{"type":23,"tag":154,"props":716,"children":717},{"style":236},[718],{"type":29,"value":265},{"type":23,"tag":154,"props":720,"children":721},{"style":236},[722],{"type":29,"value":275},{"type":23,"tag":154,"props":724,"children":725},{"style":166},[726],{"type":29,"value":727}," MusicMetadata",{"type":23,"tag":154,"props":729,"children":730},{"style":172},[731],{"type":29,"value":285},{"type":23,"tag":154,"props":733,"children":734},{"style":166},[735],{"type":29,"value":736}," ObservableObject\n",{"type":23,"tag":154,"props":738,"children":739},{"class":156,"line":188},[740],{"type":23,"tag":154,"props":741,"children":742},{"style":172},[743],{"type":29,"value":299},{"type":23,"tag":154,"props":745,"children":746},{"class":156,"line":222},[747,752,756,761,765,771],{"type":23,"tag":154,"props":748,"children":749},{"style":236},[750],{"type":29,"value":751},"    private",{"type":23,"tag":154,"props":753,"children":754},{"style":160},[755],{"type":29,"value":428},{"type":23,"tag":154,"props":757,"children":758},{"style":326},[759],{"type":29,"value":760}," _title",{"type":23,"tag":154,"props":762,"children":763},{"style":172},[764],{"type":29,"value":442},{"type":23,"tag":154,"props":766,"children":768},{"style":767},"--shiki-default:#C98A7D77",[769],{"type":29,"value":770}," \"\"",{"type":23,"tag":154,"props":772,"children":773},{"style":172},[774],{"type":29,"value":185},{"type":23,"tag":154,"props":776,"children":777},{"class":156,"line":232},[778,782,786],{"type":23,"tag":154,"props":779,"children":780},{"style":236},[781],{"type":29,"value":308},{"type":23,"tag":154,"props":783,"children":784},{"style":160},[785],{"type":29,"value":428},{"type":23,"tag":154,"props":787,"children":788},{"style":326},[789],{"type":29,"value":790}," Title\n",{"type":23,"tag":154,"props":792,"children":793},{"class":156,"line":251},[794],{"type":23,"tag":154,"props":795,"children":796},{"style":172},[797],{"type":29,"value":461},{"type":23,"tag":154,"props":799,"children":800},{"class":156,"line":259},[801,806,811,815],{"type":23,"tag":154,"props":802,"children":803},{"style":236},[804],{"type":29,"value":805},"        get",{"type":23,"tag":154,"props":807,"children":808},{"style":236},[809],{"type":29,"value":810}," =>",{"type":23,"tag":154,"props":812,"children":813},{"style":478},[814],{"type":29,"value":760},{"type":23,"tag":154,"props":816,"children":817},{"style":172},[818],{"type":29,"value":185},{"type":23,"tag":154,"props":820,"children":821},{"class":156,"line":293},[822,827,831,835,839,843,847,851,855],{"type":23,"tag":154,"props":823,"children":824},{"style":236},[825],{"type":29,"value":826},"        set",{"type":23,"tag":154,"props":828,"children":829},{"style":236},[830],{"type":29,"value":810},{"type":23,"tag":154,"props":832,"children":833},{"style":326},[834],{"type":29,"value":360},{"type":23,"tag":154,"props":836,"children":837},{"style":172},[838],{"type":29,"value":550},{"type":23,"tag":154,"props":840,"children":841},{"style":236},[842],{"type":29,"value":380},{"type":23,"tag":154,"props":844,"children":845},{"style":478},[846],{"type":29,"value":760},{"type":23,"tag":154,"props":848,"children":849},{"style":172},[850],{"type":29,"value":395},{"type":23,"tag":154,"props":852,"children":853},{"style":478},[854],{"type":29,"value":404},{"type":23,"tag":154,"props":856,"children":857},{"style":172},[858],{"type":29,"value":559},{"type":23,"tag":154,"props":860,"children":861},{"class":156,"line":302},[862],{"type":23,"tag":154,"props":863,"children":864},{"style":172},[865],{"type":29,"value":568},{"type":23,"tag":154,"props":867,"children":868},{"class":156,"line":336},[869],{"type":23,"tag":154,"props":870,"children":871},{"style":172},[872],{"type":29,"value":693},{"type":23,"tag":874,"props":875,"children":877},"h3",{"id":876},"mvvm-community-toolkit-ではダメなのか",[878],{"type":29,"value":879},"MVVM Community Toolkit ではダメなのか",{"type":23,"tag":31,"props":881,"children":882},{},[883,885,891],{"type":29,"value":884},"同じことはMVVM Community Toolkitの",{"type":23,"tag":45,"props":886,"children":888},{"className":887},[],[889],{"type":29,"value":890},"[ObservableProperty]",{"type":29,"value":892},"でソースジェネレータ任せにできます。実際、本格的な.NETアプリならそれを使う方が記述量は少なくなります。",{"type":23,"tag":31,"props":894,"children":895},{},[896,898,903],{"type":29,"value":897},"PICOMでToolkitを使わなかった理由は、この程度であれば自前実装した方が良いと判断したからです。実際、PICOMの",{"type":23,"tag":45,"props":899,"children":901},{"className":900},[],[902],{"type":29,"value":70},{"type":29,"value":904},"は20行程度で書き切れています。",{"type":23,"tag":906,"props":907,"children":910},"tradeoff-box",{"cons-label":908,"pros-label":909},"デメリット","メリット",[911,935],{"type":23,"tag":912,"props":913,"children":914},"template",{"v-slot:pros":7},[915],{"type":23,"tag":916,"props":917,"children":918},"ul",{},[919,925,930],{"type":23,"tag":920,"props":921,"children":922},"li",{},[923],{"type":29,"value":924},"依存がゼロ（Blazor WASMのバイナリサイズに寄与しない）",{"type":23,"tag":920,"props":926,"children":927},{},[928],{"type":29,"value":929},"派生クラスから見たAPIも自分で設計できる",{"type":23,"tag":920,"props":931,"children":932},{},[933],{"type":29,"value":934},"ライブラリの管理コスト削減",{"type":23,"tag":912,"props":936,"children":937},{"v-slot:cons":7},[938,949,954],{"type":23,"tag":920,"props":939,"children":940},{},[941,947],{"type":23,"tag":45,"props":942,"children":944},{"className":943},[],[945],{"type":29,"value":946},"SetProperty",{"type":29,"value":948},"を手動で呼ぶ分、記述が冗長になる",{"type":23,"tag":920,"props":950,"children":951},{},[952],{"type":29,"value":953},"比較ロジック（変更が実質あったかどうか）を自前で書く必要がある",{"type":23,"tag":920,"props":955,"children":956},{},[957,959],{"type":29,"value":958},"機能を足すと結局Toolkitと似た形に寄ってくる\n",{"type":23,"tag":916,"props":960,"children":961},{},[962],{"type":23,"tag":920,"props":963,"children":964},{},[965],{"type":29,"value":966},"あくまで低機能前提",{"type":23,"tag":24,"props":968,"children":970},{"id":969},"diffdetectableobject差分の有無をイベントで通知する",[971],{"type":29,"value":972},"DiffDetectableObject：差分の有無をイベントで通知する",{"type":23,"tag":31,"props":974,"children":975},{},[976,978,983,985,990],{"type":29,"value":977},"本題の",{"type":23,"tag":45,"props":979,"children":981},{"className":980},[],[982],{"type":29,"value":78},{"type":29,"value":984},"は、",{"type":23,"tag":45,"props":986,"children":988},{"className":987},[],[989],{"type":29,"value":70},{"type":29,"value":991},"を2つ（初期スナップショットと編集中のもの）保持するラッパーです。",{"type":23,"tag":144,"props":993,"children":995},{"className":146,"code":994,"language":148,"meta":7,"style":7},"public class DiffDetectableObject\u003CT> where T : ObservableObject, IDifferenceComparable\u003CT>\n{\n    public delegate void DiffStateChangedEventHandler(T editable, bool hasDiff);\n    public event DiffStateChangedEventHandler DiffStateChanged;\n\n    public bool HasDiff { get; private set; } = false;\n\n    private T _initObj;\n    private T _editableObj;\n\n    public DiffDetectableObject(T init, T editable)\n    {\n        _initObj = init;\n        _editableObj = editable;\n        _editableObj.PropertyChanged += Editable_PropertyChanged;\n    }\n\n    // ...\n}\n",[996],{"type":23,"tag":45,"props":997,"children":998},{"__ignoreMap":7},[999,1067,1074,1126,1150,1157,1220,1227,1247,1267,1274,1314,1321,1341,1361,1391,1398,1405,1414],{"type":23,"tag":154,"props":1000,"children":1001},{"class":156,"line":17},[1002,1006,1010,1015,1019,1023,1028,1033,1037,1041,1045,1049,1054,1058,1062],{"type":23,"tag":154,"props":1003,"children":1004},{"style":236},[1005],{"type":29,"value":265},{"type":23,"tag":154,"props":1007,"children":1008},{"style":236},[1009],{"type":29,"value":275},{"type":23,"tag":154,"props":1011,"children":1012},{"style":166},[1013],{"type":29,"value":1014}," DiffDetectableObject",{"type":23,"tag":154,"props":1016,"children":1017},{"style":172},[1018],{"type":29,"value":365},{"type":23,"tag":154,"props":1020,"children":1021},{"style":166},[1022],{"type":29,"value":370},{"type":23,"tag":154,"props":1024,"children":1025},{"style":172},[1026],{"type":29,"value":1027},">",{"type":23,"tag":154,"props":1029,"children":1030},{"style":236},[1031],{"type":29,"value":1032}," where",{"type":23,"tag":154,"props":1034,"children":1035},{"style":166},[1036],{"type":29,"value":385},{"type":23,"tag":154,"props":1038,"children":1039},{"style":172},[1040],{"type":29,"value":285},{"type":23,"tag":154,"props":1042,"children":1043},{"style":166},[1044],{"type":29,"value":280},{"type":23,"tag":154,"props":1046,"children":1047},{"style":172},[1048],{"type":29,"value":395},{"type":23,"tag":154,"props":1050,"children":1051},{"style":166},[1052],{"type":29,"value":1053}," IDifferenceComparable",{"type":23,"tag":154,"props":1055,"children":1056},{"style":172},[1057],{"type":29,"value":365},{"type":23,"tag":154,"props":1059,"children":1060},{"style":166},[1061],{"type":29,"value":370},{"type":23,"tag":154,"props":1063,"children":1064},{"style":172},[1065],{"type":29,"value":1066},">\n",{"type":23,"tag":154,"props":1068,"children":1069},{"class":156,"line":188},[1070],{"type":23,"tag":154,"props":1071,"children":1072},{"style":172},[1073],{"type":29,"value":299},{"type":23,"tag":154,"props":1075,"children":1076},{"class":156,"line":222},[1077,1081,1086,1090,1095,1099,1103,1108,1112,1117,1122],{"type":23,"tag":154,"props":1078,"children":1079},{"style":236},[1080],{"type":29,"value":308},{"type":23,"tag":154,"props":1082,"children":1083},{"style":236},[1084],{"type":29,"value":1085}," delegate",{"type":23,"tag":154,"props":1087,"children":1088},{"style":160},[1089],{"type":29,"value":355},{"type":23,"tag":154,"props":1091,"children":1092},{"style":166},[1093],{"type":29,"value":1094}," DiffStateChangedEventHandler",{"type":23,"tag":154,"props":1096,"children":1097},{"style":172},[1098],{"type":29,"value":550},{"type":23,"tag":154,"props":1100,"children":1101},{"style":166},[1102],{"type":29,"value":370},{"type":23,"tag":154,"props":1104,"children":1105},{"style":326},[1106],{"type":29,"value":1107}," editable",{"type":23,"tag":154,"props":1109,"children":1110},{"style":172},[1111],{"type":29,"value":395},{"type":23,"tag":154,"props":1113,"children":1114},{"style":160},[1115],{"type":29,"value":1116}," bool",{"type":23,"tag":154,"props":1118,"children":1119},{"style":326},[1120],{"type":29,"value":1121}," hasDiff",{"type":23,"tag":154,"props":1123,"children":1124},{"style":172},[1125],{"type":29,"value":559},{"type":23,"tag":154,"props":1127,"children":1128},{"class":156,"line":232},[1129,1133,1137,1141,1146],{"type":23,"tag":154,"props":1130,"children":1131},{"style":236},[1132],{"type":29,"value":308},{"type":23,"tag":154,"props":1134,"children":1135},{"style":236},[1136],{"type":29,"value":313},{"type":23,"tag":154,"props":1138,"children":1139},{"style":166},[1140],{"type":29,"value":1094},{"type":23,"tag":154,"props":1142,"children":1143},{"style":326},[1144],{"type":29,"value":1145}," DiffStateChanged",{"type":23,"tag":154,"props":1147,"children":1148},{"style":172},[1149],{"type":29,"value":185},{"type":23,"tag":154,"props":1151,"children":1152},{"class":156,"line":251},[1153],{"type":23,"tag":154,"props":1154,"children":1155},{"emptyLinePlaceholder":226},[1156],{"type":29,"value":229},{"type":23,"tag":154,"props":1158,"children":1159},{"class":156,"line":259},[1160,1164,1168,1173,1178,1183,1188,1193,1198,1202,1207,1211,1216],{"type":23,"tag":154,"props":1161,"children":1162},{"style":236},[1163],{"type":29,"value":308},{"type":23,"tag":154,"props":1165,"children":1166},{"style":160},[1167],{"type":29,"value":1116},{"type":23,"tag":154,"props":1169,"children":1170},{"style":326},[1171],{"type":29,"value":1172}," HasDiff",{"type":23,"tag":154,"props":1174,"children":1175},{"style":172},[1176],{"type":29,"value":1177}," {",{"type":23,"tag":154,"props":1179,"children":1180},{"style":236},[1181],{"type":29,"value":1182}," get",{"type":23,"tag":154,"props":1184,"children":1185},{"style":172},[1186],{"type":29,"value":1187},";",{"type":23,"tag":154,"props":1189,"children":1190},{"style":236},[1191],{"type":29,"value":1192}," private",{"type":23,"tag":154,"props":1194,"children":1195},{"style":236},[1196],{"type":29,"value":1197}," set",{"type":23,"tag":154,"props":1199,"children":1200},{"style":172},[1201],{"type":29,"value":1187},{"type":23,"tag":154,"props":1203,"children":1204},{"style":172},[1205],{"type":29,"value":1206}," }",{"type":23,"tag":154,"props":1208,"children":1209},{"style":172},[1210],{"type":29,"value":442},{"type":23,"tag":154,"props":1212,"children":1213},{"style":160},[1214],{"type":29,"value":1215}," false",{"type":23,"tag":154,"props":1217,"children":1218},{"style":172},[1219],{"type":29,"value":185},{"type":23,"tag":154,"props":1221,"children":1222},{"class":156,"line":293},[1223],{"type":23,"tag":154,"props":1224,"children":1225},{"emptyLinePlaceholder":226},[1226],{"type":29,"value":229},{"type":23,"tag":154,"props":1228,"children":1229},{"class":156,"line":302},[1230,1234,1238,1243],{"type":23,"tag":154,"props":1231,"children":1232},{"style":236},[1233],{"type":29,"value":751},{"type":23,"tag":154,"props":1235,"children":1236},{"style":166},[1237],{"type":29,"value":385},{"type":23,"tag":154,"props":1239,"children":1240},{"style":326},[1241],{"type":29,"value":1242}," _initObj",{"type":23,"tag":154,"props":1244,"children":1245},{"style":172},[1246],{"type":29,"value":185},{"type":23,"tag":154,"props":1248,"children":1249},{"class":156,"line":336},[1250,1254,1258,1263],{"type":23,"tag":154,"props":1251,"children":1252},{"style":236},[1253],{"type":29,"value":751},{"type":23,"tag":154,"props":1255,"children":1256},{"style":166},[1257],{"type":29,"value":385},{"type":23,"tag":154,"props":1259,"children":1260},{"style":326},[1261],{"type":29,"value":1262}," _editableObj",{"type":23,"tag":154,"props":1264,"children":1265},{"style":172},[1266],{"type":29,"value":185},{"type":23,"tag":154,"props":1268,"children":1269},{"class":156,"line":344},[1270],{"type":23,"tag":154,"props":1271,"children":1272},{"emptyLinePlaceholder":226},[1273],{"type":29,"value":229},{"type":23,"tag":154,"props":1275,"children":1276},{"class":156,"line":455},[1277,1281,1285,1289,1293,1298,1302,1306,1310],{"type":23,"tag":154,"props":1278,"children":1279},{"style":236},[1280],{"type":29,"value":308},{"type":23,"tag":154,"props":1282,"children":1283},{"style":326},[1284],{"type":29,"value":1014},{"type":23,"tag":154,"props":1286,"children":1287},{"style":172},[1288],{"type":29,"value":550},{"type":23,"tag":154,"props":1290,"children":1291},{"style":166},[1292],{"type":29,"value":370},{"type":23,"tag":154,"props":1294,"children":1295},{"style":326},[1296],{"type":29,"value":1297}," init",{"type":23,"tag":154,"props":1299,"children":1300},{"style":172},[1301],{"type":29,"value":395},{"type":23,"tag":154,"props":1303,"children":1304},{"style":166},[1305],{"type":29,"value":385},{"type":23,"tag":154,"props":1307,"children":1308},{"style":326},[1309],{"type":29,"value":1107},{"type":23,"tag":154,"props":1311,"children":1312},{"style":172},[1313],{"type":29,"value":452},{"type":23,"tag":154,"props":1315,"children":1316},{"class":156,"line":464},[1317],{"type":23,"tag":154,"props":1318,"children":1319},{"style":172},[1320],{"type":29,"value":461},{"type":23,"tag":154,"props":1322,"children":1323},{"class":156,"line":518},[1324,1329,1333,1337],{"type":23,"tag":154,"props":1325,"children":1326},{"style":478},[1327],{"type":29,"value":1328},"        _initObj",{"type":23,"tag":154,"props":1330,"children":1331},{"style":172},[1332],{"type":29,"value":442},{"type":23,"tag":154,"props":1334,"children":1335},{"style":478},[1336],{"type":29,"value":1297},{"type":23,"tag":154,"props":1338,"children":1339},{"style":172},[1340],{"type":29,"value":185},{"type":23,"tag":154,"props":1342,"children":1343},{"class":156,"line":539},[1344,1349,1353,1357],{"type":23,"tag":154,"props":1345,"children":1346},{"style":478},[1347],{"type":29,"value":1348},"        _editableObj",{"type":23,"tag":154,"props":1350,"children":1351},{"style":172},[1352],{"type":29,"value":442},{"type":23,"tag":154,"props":1354,"children":1355},{"style":478},[1356],{"type":29,"value":1107},{"type":23,"tag":154,"props":1358,"children":1359},{"style":172},[1360],{"type":29,"value":185},{"type":23,"tag":154,"props":1362,"children":1363},{"class":156,"line":562},[1364,1368,1372,1377,1382,1387],{"type":23,"tag":154,"props":1365,"children":1366},{"style":478},[1367],{"type":29,"value":1348},{"type":23,"tag":154,"props":1369,"children":1370},{"style":172},[1371],{"type":29,"value":175},{"type":23,"tag":154,"props":1373,"children":1374},{"style":478},[1375],{"type":29,"value":1376},"PropertyChanged",{"type":23,"tag":154,"props":1378,"children":1379},{"style":236},[1380],{"type":29,"value":1381}," +=",{"type":23,"tag":154,"props":1383,"children":1384},{"style":478},[1385],{"type":29,"value":1386}," Editable_PropertyChanged",{"type":23,"tag":154,"props":1388,"children":1389},{"style":172},[1390],{"type":29,"value":185},{"type":23,"tag":154,"props":1392,"children":1393},{"class":156,"line":571},[1394],{"type":23,"tag":154,"props":1395,"children":1396},{"style":172},[1397],{"type":29,"value":568},{"type":23,"tag":154,"props":1399,"children":1400},{"class":156,"line":579},[1401],{"type":23,"tag":154,"props":1402,"children":1403},{"emptyLinePlaceholder":226},[1404],{"type":29,"value":229},{"type":23,"tag":154,"props":1406,"children":1407},{"class":156,"line":613},[1408],{"type":23,"tag":154,"props":1409,"children":1411},{"style":1410},"--shiki-default:#758575DD",[1412],{"type":29,"value":1413},"    // ...\n",{"type":23,"tag":154,"props":1415,"children":1416},{"class":156,"line":621},[1417],{"type":23,"tag":154,"props":1418,"children":1419},{"style":172},[1420],{"type":29,"value":693},{"type":23,"tag":31,"props":1422,"children":1423},{},[1424,1426,1432,1433,1439,1441,1446,1448,1453,1455,1460,1462,1468,1470,1475],{"type":29,"value":1425},"肝は",{"type":23,"tag":45,"props":1427,"children":1429},{"className":1428},[],[1430],{"type":29,"value":1431},"_initObj",{"type":29,"value":72},{"type":23,"tag":45,"props":1434,"children":1436},{"className":1435},[],[1437],{"type":29,"value":1438},"_editableObj",{"type":29,"value":1440},"の2本持ちです。",{"type":23,"tag":45,"props":1442,"children":1444},{"className":1443},[],[1445],{"type":29,"value":1431},{"type":29,"value":1447},"は「最後に保存した時点のデータ」、",{"type":23,"tag":45,"props":1449,"children":1451},{"className":1450},[],[1452],{"type":29,"value":1438},{"type":29,"value":1454},"は「編集中のデータ」を指し、どちらも同じ型",{"type":23,"tag":45,"props":1456,"children":1458},{"className":1457},[],[1459],{"type":29,"value":370},{"type":29,"value":1461},"で持ちます。",{"type":23,"tag":45,"props":1463,"children":1465},{"className":1464},[],[1466],{"type":29,"value":1467},"where T : ObservableObject, IDifferenceComparable\u003CT>",{"type":29,"value":1469},"という型制約で、",{"type":23,"tag":45,"props":1471,"children":1473},{"className":1472},[],[1474],{"type":29,"value":370},{"type":29,"value":1476},"は必ず「変更通知できる」「比較できる」の2つを満たすことを強制しています。",{"type":23,"tag":31,"props":1478,"children":1479},{},[1480,1486,1488,1494],{"type":23,"tag":45,"props":1481,"children":1483},{"className":1482},[],[1484],{"type":29,"value":1485},"IDifferenceComparable\u003CT>",{"type":29,"value":1487},"は自前の最小インターフェースで、中身は次のように",{"type":23,"tag":45,"props":1489,"children":1491},{"className":1490},[],[1492],{"type":29,"value":1493},"CompareDifference",{"type":29,"value":1495},"メソッド1本だけです。",{"type":23,"tag":144,"props":1497,"children":1499},{"className":146,"code":1498,"language":148,"meta":7,"style":7},"public interface IDifferenceComparable\u003CTSelf>\n{\n    /// \u003Csummary>差分がある場合:true\u003C/summary>\n    bool CompareDifference(TSelf other);\n}\n",[1500],{"type":23,"tag":45,"props":1501,"children":1502},{"__ignoreMap":7},[1503,1532,1539,1578,1608],{"type":23,"tag":154,"props":1504,"children":1505},{"class":156,"line":17},[1506,1510,1515,1519,1523,1528],{"type":23,"tag":154,"props":1507,"children":1508},{"style":236},[1509],{"type":29,"value":265},{"type":23,"tag":154,"props":1511,"children":1512},{"style":236},[1513],{"type":29,"value":1514}," interface",{"type":23,"tag":154,"props":1516,"children":1517},{"style":166},[1518],{"type":29,"value":1053},{"type":23,"tag":154,"props":1520,"children":1521},{"style":172},[1522],{"type":29,"value":365},{"type":23,"tag":154,"props":1524,"children":1525},{"style":166},[1526],{"type":29,"value":1527},"TSelf",{"type":23,"tag":154,"props":1529,"children":1530},{"style":172},[1531],{"type":29,"value":1066},{"type":23,"tag":154,"props":1533,"children":1534},{"class":156,"line":188},[1535],{"type":23,"tag":154,"props":1536,"children":1537},{"style":172},[1538],{"type":29,"value":299},{"type":23,"tag":154,"props":1540,"children":1541},{"class":156,"line":222},[1542,1547,1551,1556,1560,1565,1570,1574],{"type":23,"tag":154,"props":1543,"children":1544},{"style":1410},[1545],{"type":29,"value":1546},"    /// ",{"type":23,"tag":154,"props":1548,"children":1549},{"style":172},[1550],{"type":29,"value":365},{"type":23,"tag":154,"props":1552,"children":1553},{"style":160},[1554],{"type":29,"value":1555},"summary",{"type":23,"tag":154,"props":1557,"children":1558},{"style":172},[1559],{"type":29,"value":1027},{"type":23,"tag":154,"props":1561,"children":1562},{"style":1410},[1563],{"type":29,"value":1564},"差分がある場合:true",{"type":23,"tag":154,"props":1566,"children":1567},{"style":172},[1568],{"type":29,"value":1569},"\u003C/",{"type":23,"tag":154,"props":1571,"children":1572},{"style":160},[1573],{"type":29,"value":1555},{"type":23,"tag":154,"props":1575,"children":1576},{"style":172},[1577],{"type":29,"value":1066},{"type":23,"tag":154,"props":1579,"children":1580},{"class":156,"line":232},[1581,1586,1591,1595,1599,1604],{"type":23,"tag":154,"props":1582,"children":1583},{"style":160},[1584],{"type":29,"value":1585},"    bool",{"type":23,"tag":154,"props":1587,"children":1588},{"style":326},[1589],{"type":29,"value":1590}," CompareDifference",{"type":23,"tag":154,"props":1592,"children":1593},{"style":172},[1594],{"type":29,"value":550},{"type":23,"tag":154,"props":1596,"children":1597},{"style":166},[1598],{"type":29,"value":1527},{"type":23,"tag":154,"props":1600,"children":1601},{"style":326},[1602],{"type":29,"value":1603}," other",{"type":23,"tag":154,"props":1605,"children":1606},{"style":172},[1607],{"type":29,"value":559},{"type":23,"tag":154,"props":1609,"children":1610},{"class":156,"line":251},[1611],{"type":23,"tag":154,"props":1612,"children":1613},{"style":172},[1614],{"type":29,"value":693},{"type":23,"tag":874,"props":1616,"children":1618},{"id":1617},"プロパティ変更を検知してフラグを切り替える",[1619],{"type":29,"value":1617},{"type":23,"tag":31,"props":1621,"children":1622},{},[1623,1628],{"type":23,"tag":45,"props":1624,"children":1626},{"className":1625},[],[1627],{"type":29,"value":1376},{"type":29,"value":1629},"イベントをフックして、そのたびに差分状態を再計算します。",{"type":23,"tag":144,"props":1631,"children":1633},{"className":146,"code":1632,"language":148,"meta":7,"style":7},"private void Editable_PropertyChanged(object sender, PropertyChangedEventArgs e)\n{\n    // 既に差分がある状態で、差分がなくなった場合\n    if (!_initObj.CompareDifference(_editableObj) && HasDiff)\n    {\n        HasDiff = false;\n        DiffStateChanged?.Invoke(_editableObj, false);\n    }\n    // 差分がない状態で差分が発生した場合\n    else if (_initObj.CompareDifference(_editableObj) && !HasDiff)\n    {\n        HasDiff = true;\n        DiffStateChanged?.Invoke(_editableObj, true);\n    }\n}\n",[1634],{"type":23,"tag":45,"props":1635,"children":1636},{"__ignoreMap":7},[1637,1684,1691,1699,1753,1760,1780,1820,1827,1835,1894,1901,1921,1960,1967],{"type":23,"tag":154,"props":1638,"children":1639},{"class":156,"line":17},[1640,1645,1649,1653,1657,1662,1667,1671,1675,1680],{"type":23,"tag":154,"props":1641,"children":1642},{"style":236},[1643],{"type":29,"value":1644},"private",{"type":23,"tag":154,"props":1646,"children":1647},{"style":160},[1648],{"type":29,"value":355},{"type":23,"tag":154,"props":1650,"children":1651},{"style":326},[1652],{"type":29,"value":1386},{"type":23,"tag":154,"props":1654,"children":1655},{"style":172},[1656],{"type":29,"value":550},{"type":23,"tag":154,"props":1658,"children":1659},{"style":160},[1660],{"type":29,"value":1661},"object",{"type":23,"tag":154,"props":1663,"children":1664},{"style":326},[1665],{"type":29,"value":1666}," sender",{"type":23,"tag":154,"props":1668,"children":1669},{"style":172},[1670],{"type":29,"value":395},{"type":23,"tag":154,"props":1672,"children":1673},{"style":166},[1674],{"type":29,"value":663},{"type":23,"tag":154,"props":1676,"children":1677},{"style":326},[1678],{"type":29,"value":1679}," e",{"type":23,"tag":154,"props":1681,"children":1682},{"style":172},[1683],{"type":29,"value":452},{"type":23,"tag":154,"props":1685,"children":1686},{"class":156,"line":188},[1687],{"type":23,"tag":154,"props":1688,"children":1689},{"style":172},[1690],{"type":29,"value":299},{"type":23,"tag":154,"props":1692,"children":1693},{"class":156,"line":222},[1694],{"type":23,"tag":154,"props":1695,"children":1696},{"style":1410},[1697],{"type":29,"value":1698},"    // 既に差分がある状態で、差分がなくなった場合\n",{"type":23,"tag":154,"props":1700,"children":1701},{"class":156,"line":232},[1702,1707,1711,1716,1720,1724,1728,1732,1736,1740,1745,1749],{"type":23,"tag":154,"props":1703,"children":1704},{"style":160},[1705],{"type":29,"value":1706},"    if",{"type":23,"tag":154,"props":1708,"children":1709},{"style":172},[1710],{"type":29,"value":475},{"type":23,"tag":154,"props":1712,"children":1713},{"style":236},[1714],{"type":29,"value":1715},"!",{"type":23,"tag":154,"props":1717,"children":1718},{"style":478},[1719],{"type":29,"value":1431},{"type":23,"tag":154,"props":1721,"children":1722},{"style":172},[1723],{"type":29,"value":175},{"type":23,"tag":154,"props":1725,"children":1726},{"style":326},[1727],{"type":29,"value":1493},{"type":23,"tag":154,"props":1729,"children":1730},{"style":172},[1731],{"type":29,"value":550},{"type":23,"tag":154,"props":1733,"children":1734},{"style":478},[1735],{"type":29,"value":1438},{"type":23,"tag":154,"props":1737,"children":1738},{"style":172},[1739],{"type":29,"value":495},{"type":23,"tag":154,"props":1741,"children":1742},{"style":236},[1743],{"type":29,"value":1744}," &&",{"type":23,"tag":154,"props":1746,"children":1747},{"style":478},[1748],{"type":29,"value":1172},{"type":23,"tag":154,"props":1750,"children":1751},{"style":172},[1752],{"type":29,"value":452},{"type":23,"tag":154,"props":1754,"children":1755},{"class":156,"line":251},[1756],{"type":23,"tag":154,"props":1757,"children":1758},{"style":172},[1759],{"type":29,"value":461},{"type":23,"tag":154,"props":1761,"children":1762},{"class":156,"line":259},[1763,1768,1772,1776],{"type":23,"tag":154,"props":1764,"children":1765},{"style":478},[1766],{"type":29,"value":1767},"        HasDiff",{"type":23,"tag":154,"props":1769,"children":1770},{"style":172},[1771],{"type":29,"value":442},{"type":23,"tag":154,"props":1773,"children":1774},{"style":160},[1775],{"type":29,"value":1215},{"type":23,"tag":154,"props":1777,"children":1778},{"style":172},[1779],{"type":29,"value":185},{"type":23,"tag":154,"props":1781,"children":1782},{"class":156,"line":293},[1783,1788,1792,1796,1800,1804,1808,1812,1816],{"type":23,"tag":154,"props":1784,"children":1785},{"style":478},[1786],{"type":29,"value":1787},"        DiffStateChanged",{"type":23,"tag":154,"props":1789,"children":1790},{"style":236},[1791],{"type":29,"value":323},{"type":23,"tag":154,"props":1793,"children":1794},{"style":172},[1795],{"type":29,"value":175},{"type":23,"tag":154,"props":1797,"children":1798},{"style":326},[1799],{"type":29,"value":640},{"type":23,"tag":154,"props":1801,"children":1802},{"style":172},[1803],{"type":29,"value":550},{"type":23,"tag":154,"props":1805,"children":1806},{"style":478},[1807],{"type":29,"value":1438},{"type":23,"tag":154,"props":1809,"children":1810},{"style":172},[1811],{"type":29,"value":395},{"type":23,"tag":154,"props":1813,"children":1814},{"style":160},[1815],{"type":29,"value":1215},{"type":23,"tag":154,"props":1817,"children":1818},{"style":172},[1819],{"type":29,"value":559},{"type":23,"tag":154,"props":1821,"children":1822},{"class":156,"line":302},[1823],{"type":23,"tag":154,"props":1824,"children":1825},{"style":172},[1826],{"type":29,"value":568},{"type":23,"tag":154,"props":1828,"children":1829},{"class":156,"line":336},[1830],{"type":23,"tag":154,"props":1831,"children":1832},{"style":1410},[1833],{"type":29,"value":1834},"    // 差分がない状態で差分が発生した場合\n",{"type":23,"tag":154,"props":1836,"children":1837},{"class":156,"line":344},[1838,1843,1848,1852,1856,1860,1864,1868,1872,1876,1880,1885,1890],{"type":23,"tag":154,"props":1839,"children":1840},{"style":160},[1841],{"type":29,"value":1842},"    else",{"type":23,"tag":154,"props":1844,"children":1845},{"style":160},[1846],{"type":29,"value":1847}," if",{"type":23,"tag":154,"props":1849,"children":1850},{"style":172},[1851],{"type":29,"value":475},{"type":23,"tag":154,"props":1853,"children":1854},{"style":478},[1855],{"type":29,"value":1431},{"type":23,"tag":154,"props":1857,"children":1858},{"style":172},[1859],{"type":29,"value":175},{"type":23,"tag":154,"props":1861,"children":1862},{"style":326},[1863],{"type":29,"value":1493},{"type":23,"tag":154,"props":1865,"children":1866},{"style":172},[1867],{"type":29,"value":550},{"type":23,"tag":154,"props":1869,"children":1870},{"style":478},[1871],{"type":29,"value":1438},{"type":23,"tag":154,"props":1873,"children":1874},{"style":172},[1875],{"type":29,"value":495},{"type":23,"tag":154,"props":1877,"children":1878},{"style":236},[1879],{"type":29,"value":1744},{"type":23,"tag":154,"props":1881,"children":1882},{"style":236},[1883],{"type":29,"value":1884}," !",{"type":23,"tag":154,"props":1886,"children":1887},{"style":478},[1888],{"type":29,"value":1889},"HasDiff",{"type":23,"tag":154,"props":1891,"children":1892},{"style":172},[1893],{"type":29,"value":452},{"type":23,"tag":154,"props":1895,"children":1896},{"class":156,"line":455},[1897],{"type":23,"tag":154,"props":1898,"children":1899},{"style":172},[1900],{"type":29,"value":461},{"type":23,"tag":154,"props":1902,"children":1903},{"class":156,"line":464},[1904,1908,1912,1917],{"type":23,"tag":154,"props":1905,"children":1906},{"style":478},[1907],{"type":29,"value":1767},{"type":23,"tag":154,"props":1909,"children":1910},{"style":172},[1911],{"type":29,"value":442},{"type":23,"tag":154,"props":1913,"children":1914},{"style":160},[1915],{"type":29,"value":1916}," true",{"type":23,"tag":154,"props":1918,"children":1919},{"style":172},[1920],{"type":29,"value":185},{"type":23,"tag":154,"props":1922,"children":1923},{"class":156,"line":518},[1924,1928,1932,1936,1940,1944,1948,1952,1956],{"type":23,"tag":154,"props":1925,"children":1926},{"style":478},[1927],{"type":29,"value":1787},{"type":23,"tag":154,"props":1929,"children":1930},{"style":236},[1931],{"type":29,"value":323},{"type":23,"tag":154,"props":1933,"children":1934},{"style":172},[1935],{"type":29,"value":175},{"type":23,"tag":154,"props":1937,"children":1938},{"style":326},[1939],{"type":29,"value":640},{"type":23,"tag":154,"props":1941,"children":1942},{"style":172},[1943],{"type":29,"value":550},{"type":23,"tag":154,"props":1945,"children":1946},{"style":478},[1947],{"type":29,"value":1438},{"type":23,"tag":154,"props":1949,"children":1950},{"style":172},[1951],{"type":29,"value":395},{"type":23,"tag":154,"props":1953,"children":1954},{"style":160},[1955],{"type":29,"value":1916},{"type":23,"tag":154,"props":1957,"children":1958},{"style":172},[1959],{"type":29,"value":559},{"type":23,"tag":154,"props":1961,"children":1962},{"class":156,"line":539},[1963],{"type":23,"tag":154,"props":1964,"children":1965},{"style":172},[1966],{"type":29,"value":568},{"type":23,"tag":154,"props":1968,"children":1969},{"class":156,"line":562},[1970],{"type":23,"tag":154,"props":1971,"children":1972},{"style":172},[1973],{"type":29,"value":693},{"type":23,"tag":31,"props":1975,"children":1976},{},[1977,1979,1984,1986,1991],{"type":29,"value":1978},"ここで工夫している点は、",{"type":23,"tag":37,"props":1980,"children":1981},{},[1982],{"type":29,"value":1983},"「フラグが変化した瞬間だけ」イベントを発火",{"type":29,"value":1985},"している点です。毎回",{"type":23,"tag":45,"props":1987,"children":1989},{"className":1988},[],[1990],{"type":29,"value":1376},{"type":29,"value":1992},"のたびにイベントを流してしまうと、Blazorのレンダリングループに過剰な負荷がかかります。PICOMの楽譜エディタはピアノロールをドラッグするだけで毎秒数十回のプロパティ変更が走るので、イベントが暴れ出さないように「差分あり↔差分なし」の遷移時だけに絞っています。",{"type":23,"tag":24,"props":1994,"children":1996},{"id":1995},"実際の使われ方保存ボタンの活性判定",[1997],{"type":29,"value":1998},"実際の使われ方：保存ボタンの活性判定",{"type":23,"tag":31,"props":2000,"children":2001},{},[2002],{"type":29,"value":2003},"ViewModel側では次のような感じで使います。",{"type":23,"tag":144,"props":2005,"children":2007},{"className":146,"code":2006,"language":148,"meta":7,"style":7},"var initial = LoadFromStorage();          // 初期データ\nvar editable = initial.Clone();           // 編集可能なコピー\n_diff = new DiffDetectableObject\u003CMusic>(initial, editable);\n\n_diff.DiffStateChanged += (music, hasDiff) =>\n{\n    CanSave = hasDiff;\n    TitleBarMark = hasDiff ? \"*\" : \"\";\n    StateHasChanged();\n};\n",[2008],{"type":23,"tag":45,"props":2009,"children":2010},{"__ignoreMap":7},[2011,2043,2080,2130,2137,2183,2190,2210,2258,2270],{"type":23,"tag":154,"props":2012,"children":2013},{"class":156,"line":17},[2014,2019,2024,2028,2033,2038],{"type":23,"tag":154,"props":2015,"children":2016},{"style":236},[2017],{"type":29,"value":2018},"var",{"type":23,"tag":154,"props":2020,"children":2021},{"style":326},[2022],{"type":29,"value":2023}," initial",{"type":23,"tag":154,"props":2025,"children":2026},{"style":172},[2027],{"type":29,"value":442},{"type":23,"tag":154,"props":2029,"children":2030},{"style":326},[2031],{"type":29,"value":2032}," LoadFromStorage",{"type":23,"tag":154,"props":2034,"children":2035},{"style":172},[2036],{"type":29,"value":2037},"();",{"type":23,"tag":154,"props":2039,"children":2040},{"style":1410},[2041],{"type":29,"value":2042},"          // 初期データ\n",{"type":23,"tag":154,"props":2044,"children":2045},{"class":156,"line":188},[2046,2050,2054,2058,2062,2066,2071,2075],{"type":23,"tag":154,"props":2047,"children":2048},{"style":236},[2049],{"type":29,"value":2018},{"type":23,"tag":154,"props":2051,"children":2052},{"style":326},[2053],{"type":29,"value":1107},{"type":23,"tag":154,"props":2055,"children":2056},{"style":172},[2057],{"type":29,"value":442},{"type":23,"tag":154,"props":2059,"children":2060},{"style":478},[2061],{"type":29,"value":2023},{"type":23,"tag":154,"props":2063,"children":2064},{"style":172},[2065],{"type":29,"value":175},{"type":23,"tag":154,"props":2067,"children":2068},{"style":326},[2069],{"type":29,"value":2070},"Clone",{"type":23,"tag":154,"props":2072,"children":2073},{"style":172},[2074],{"type":29,"value":2037},{"type":23,"tag":154,"props":2076,"children":2077},{"style":1410},[2078],{"type":29,"value":2079},"           // 編集可能なコピー\n",{"type":23,"tag":154,"props":2081,"children":2082},{"class":156,"line":222},[2083,2088,2092,2096,2100,2104,2109,2113,2118,2122,2126],{"type":23,"tag":154,"props":2084,"children":2085},{"style":478},[2086],{"type":29,"value":2087},"_diff",{"type":23,"tag":154,"props":2089,"children":2090},{"style":172},[2091],{"type":29,"value":442},{"type":23,"tag":154,"props":2093,"children":2094},{"style":236},[2095],{"type":29,"value":505},{"type":23,"tag":154,"props":2097,"children":2098},{"style":166},[2099],{"type":29,"value":1014},{"type":23,"tag":154,"props":2101,"children":2102},{"style":172},[2103],{"type":29,"value":365},{"type":23,"tag":154,"props":2105,"children":2106},{"style":166},[2107],{"type":29,"value":2108},"Music",{"type":23,"tag":154,"props":2110,"children":2111},{"style":172},[2112],{"type":29,"value":375},{"type":23,"tag":154,"props":2114,"children":2115},{"style":478},[2116],{"type":29,"value":2117},"initial",{"type":23,"tag":154,"props":2119,"children":2120},{"style":172},[2121],{"type":29,"value":395},{"type":23,"tag":154,"props":2123,"children":2124},{"style":478},[2125],{"type":29,"value":1107},{"type":23,"tag":154,"props":2127,"children":2128},{"style":172},[2129],{"type":29,"value":559},{"type":23,"tag":154,"props":2131,"children":2132},{"class":156,"line":232},[2133],{"type":23,"tag":154,"props":2134,"children":2135},{"emptyLinePlaceholder":226},[2136],{"type":29,"value":229},{"type":23,"tag":154,"props":2138,"children":2139},{"class":156,"line":251},[2140,2144,2148,2153,2157,2161,2166,2170,2174,2178],{"type":23,"tag":154,"props":2141,"children":2142},{"style":478},[2143],{"type":29,"value":2087},{"type":23,"tag":154,"props":2145,"children":2146},{"style":172},[2147],{"type":29,"value":175},{"type":23,"tag":154,"props":2149,"children":2150},{"style":478},[2151],{"type":29,"value":2152},"DiffStateChanged",{"type":23,"tag":154,"props":2154,"children":2155},{"style":236},[2156],{"type":29,"value":1381},{"type":23,"tag":154,"props":2158,"children":2159},{"style":172},[2160],{"type":29,"value":475},{"type":23,"tag":154,"props":2162,"children":2163},{"style":326},[2164],{"type":29,"value":2165},"music",{"type":23,"tag":154,"props":2167,"children":2168},{"style":172},[2169],{"type":29,"value":395},{"type":23,"tag":154,"props":2171,"children":2172},{"style":326},[2173],{"type":29,"value":1121},{"type":23,"tag":154,"props":2175,"children":2176},{"style":172},[2177],{"type":29,"value":495},{"type":23,"tag":154,"props":2179,"children":2180},{"style":236},[2181],{"type":29,"value":2182}," =>\n",{"type":23,"tag":154,"props":2184,"children":2185},{"class":156,"line":259},[2186],{"type":23,"tag":154,"props":2187,"children":2188},{"style":172},[2189],{"type":29,"value":299},{"type":23,"tag":154,"props":2191,"children":2192},{"class":156,"line":293},[2193,2198,2202,2206],{"type":23,"tag":154,"props":2194,"children":2195},{"style":478},[2196],{"type":29,"value":2197},"    CanSave",{"type":23,"tag":154,"props":2199,"children":2200},{"style":172},[2201],{"type":29,"value":442},{"type":23,"tag":154,"props":2203,"children":2204},{"style":478},[2205],{"type":29,"value":1121},{"type":23,"tag":154,"props":2207,"children":2208},{"style":172},[2209],{"type":29,"value":185},{"type":23,"tag":154,"props":2211,"children":2212},{"class":156,"line":302},[2213,2218,2222,2226,2231,2236,2241,2246,2250,2254],{"type":23,"tag":154,"props":2214,"children":2215},{"style":478},[2216],{"type":29,"value":2217},"    TitleBarMark",{"type":23,"tag":154,"props":2219,"children":2220},{"style":172},[2221],{"type":29,"value":442},{"type":23,"tag":154,"props":2223,"children":2224},{"style":478},[2225],{"type":29,"value":1121},{"type":23,"tag":154,"props":2227,"children":2228},{"style":236},[2229],{"type":29,"value":2230}," ?",{"type":23,"tag":154,"props":2232,"children":2233},{"style":767},[2234],{"type":29,"value":2235}," \"",{"type":23,"tag":154,"props":2237,"children":2239},{"style":2238},"--shiki-default:#C98A7D",[2240],{"type":29,"value":50},{"type":23,"tag":154,"props":2242,"children":2243},{"style":767},[2244],{"type":29,"value":2245},"\"",{"type":23,"tag":154,"props":2247,"children":2248},{"style":236},[2249],{"type":29,"value":285},{"type":23,"tag":154,"props":2251,"children":2252},{"style":767},[2253],{"type":29,"value":770},{"type":23,"tag":154,"props":2255,"children":2256},{"style":172},[2257],{"type":29,"value":185},{"type":23,"tag":154,"props":2259,"children":2260},{"class":156,"line":336},[2261,2266],{"type":23,"tag":154,"props":2262,"children":2263},{"style":326},[2264],{"type":29,"value":2265},"    StateHasChanged",{"type":23,"tag":154,"props":2267,"children":2268},{"style":172},[2269],{"type":29,"value":515},{"type":23,"tag":154,"props":2271,"children":2272},{"class":156,"line":344},[2273],{"type":23,"tag":154,"props":2274,"children":2275},{"style":172},[2276],{"type":29,"value":2277},"};\n",{"type":23,"tag":31,"props":2279,"children":2280},{},[2281,2283,2289,2291,2296,2298,2304,2306,2312],{"type":29,"value":2282},"UI側は",{"type":23,"tag":45,"props":2284,"children":2286},{"className":2285},[],[2287],{"type":29,"value":2288},"CanSave",{"type":29,"value":2290},"プロパティをそのままボタンの活性（enabled）にバインドすれば終わりです。ユーザーが何か変更したら",{"type":23,"tag":45,"props":2292,"children":2294},{"className":2293},[],[2295],{"type":29,"value":1889},{"type":29,"value":2297},"が",{"type":23,"tag":45,"props":2299,"children":2301},{"className":2300},[],[2302],{"type":29,"value":2303},"true",{"type":29,"value":2305},"になり、保存後に",{"type":23,"tag":45,"props":2307,"children":2309},{"className":2308},[],[2310],{"type":29,"value":2311},"EditableToInit",{"type":29,"value":2313},"を呼んで初期状態を「今」に巻き戻します。",{"type":23,"tag":144,"props":2315,"children":2317},{"className":146,"code":2316,"language":148,"meta":7,"style":7},"public void EditableToInit(T newEditable)\n{\n    _initObj = _editableObj;\n    Value = newEditable;\n    HasDiff = false;\n    DiffStateChanged?.Invoke(_editableObj, false);\n}\n",[2318],{"type":23,"tag":45,"props":2319,"children":2320},{"__ignoreMap":7},[2321,2354,2361,2381,2401,2421,2461],{"type":23,"tag":154,"props":2322,"children":2323},{"class":156,"line":17},[2324,2328,2332,2337,2341,2345,2350],{"type":23,"tag":154,"props":2325,"children":2326},{"style":236},[2327],{"type":29,"value":265},{"type":23,"tag":154,"props":2329,"children":2330},{"style":160},[2331],{"type":29,"value":355},{"type":23,"tag":154,"props":2333,"children":2334},{"style":326},[2335],{"type":29,"value":2336}," EditableToInit",{"type":23,"tag":154,"props":2338,"children":2339},{"style":172},[2340],{"type":29,"value":550},{"type":23,"tag":154,"props":2342,"children":2343},{"style":166},[2344],{"type":29,"value":370},{"type":23,"tag":154,"props":2346,"children":2347},{"style":326},[2348],{"type":29,"value":2349}," newEditable",{"type":23,"tag":154,"props":2351,"children":2352},{"style":172},[2353],{"type":29,"value":452},{"type":23,"tag":154,"props":2355,"children":2356},{"class":156,"line":188},[2357],{"type":23,"tag":154,"props":2358,"children":2359},{"style":172},[2360],{"type":29,"value":299},{"type":23,"tag":154,"props":2362,"children":2363},{"class":156,"line":222},[2364,2369,2373,2377],{"type":23,"tag":154,"props":2365,"children":2366},{"style":478},[2367],{"type":29,"value":2368},"    _initObj",{"type":23,"tag":154,"props":2370,"children":2371},{"style":172},[2372],{"type":29,"value":442},{"type":23,"tag":154,"props":2374,"children":2375},{"style":478},[2376],{"type":29,"value":1262},{"type":23,"tag":154,"props":2378,"children":2379},{"style":172},[2380],{"type":29,"value":185},{"type":23,"tag":154,"props":2382,"children":2383},{"class":156,"line":232},[2384,2389,2393,2397],{"type":23,"tag":154,"props":2385,"children":2386},{"style":478},[2387],{"type":29,"value":2388},"    Value",{"type":23,"tag":154,"props":2390,"children":2391},{"style":172},[2392],{"type":29,"value":442},{"type":23,"tag":154,"props":2394,"children":2395},{"style":478},[2396],{"type":29,"value":2349},{"type":23,"tag":154,"props":2398,"children":2399},{"style":172},[2400],{"type":29,"value":185},{"type":23,"tag":154,"props":2402,"children":2403},{"class":156,"line":251},[2404,2409,2413,2417],{"type":23,"tag":154,"props":2405,"children":2406},{"style":478},[2407],{"type":29,"value":2408},"    HasDiff",{"type":23,"tag":154,"props":2410,"children":2411},{"style":172},[2412],{"type":29,"value":442},{"type":23,"tag":154,"props":2414,"children":2415},{"style":160},[2416],{"type":29,"value":1215},{"type":23,"tag":154,"props":2418,"children":2419},{"style":172},[2420],{"type":29,"value":185},{"type":23,"tag":154,"props":2422,"children":2423},{"class":156,"line":259},[2424,2429,2433,2437,2441,2445,2449,2453,2457],{"type":23,"tag":154,"props":2425,"children":2426},{"style":478},[2427],{"type":29,"value":2428},"    DiffStateChanged",{"type":23,"tag":154,"props":2430,"children":2431},{"style":236},[2432],{"type":29,"value":323},{"type":23,"tag":154,"props":2434,"children":2435},{"style":172},[2436],{"type":29,"value":175},{"type":23,"tag":154,"props":2438,"children":2439},{"style":326},[2440],{"type":29,"value":640},{"type":23,"tag":154,"props":2442,"children":2443},{"style":172},[2444],{"type":29,"value":550},{"type":23,"tag":154,"props":2446,"children":2447},{"style":478},[2448],{"type":29,"value":1438},{"type":23,"tag":154,"props":2450,"children":2451},{"style":172},[2452],{"type":29,"value":395},{"type":23,"tag":154,"props":2454,"children":2455},{"style":160},[2456],{"type":29,"value":1215},{"type":23,"tag":154,"props":2458,"children":2459},{"style":172},[2460],{"type":29,"value":559},{"type":23,"tag":154,"props":2462,"children":2463},{"class":156,"line":293},[2464],{"type":23,"tag":154,"props":2465,"children":2466},{"style":172},[2467],{"type":29,"value":693},{"type":23,"tag":24,"props":2469,"children":2471},{"id":2470},"注意点propertychanged解除忘れでメモリリーク",[2472],{"type":29,"value":2473},"注意点：PropertyChanged解除忘れでメモリリーク",{"type":23,"tag":31,"props":2475,"children":2476},{},[2477,2479,2484],{"type":29,"value":2478},"プロパティに新しいオブジェクトをセットしたときの",{"type":23,"tag":37,"props":2480,"children":2481},{},[2482],{"type":29,"value":2483},"イベント解除",{"type":29,"value":2485},"を忘れると、イベント購読がどんどん溜まる現象が発生します。",{"type":23,"tag":144,"props":2487,"children":2489},{"className":146,"code":2488,"language":148,"meta":7,"style":7},"public T Value\n{\n    get => _editableObj;\n    set\n    {\n        _editableObj.PropertyChanged -= Editable_PropertyChanged;  // ← 忘れがち\n        _editableObj = value;\n        _editableObj.PropertyChanged += Editable_PropertyChanged;\n    }\n}\n",[2490],{"type":23,"tag":45,"props":2491,"children":2492},{"__ignoreMap":7},[2493,2509,2516,2536,2544,2551,2584,2603,2630,2637],{"type":23,"tag":154,"props":2494,"children":2495},{"class":156,"line":17},[2496,2500,2504],{"type":23,"tag":154,"props":2497,"children":2498},{"style":236},[2499],{"type":29,"value":265},{"type":23,"tag":154,"props":2501,"children":2502},{"style":478},[2503],{"type":29,"value":385},{"type":23,"tag":154,"props":2505,"children":2506},{"style":478},[2507],{"type":29,"value":2508}," Value\n",{"type":23,"tag":154,"props":2510,"children":2511},{"class":156,"line":188},[2512],{"type":23,"tag":154,"props":2513,"children":2514},{"style":172},[2515],{"type":29,"value":299},{"type":23,"tag":154,"props":2517,"children":2518},{"class":156,"line":222},[2519,2524,2528,2532],{"type":23,"tag":154,"props":2520,"children":2521},{"style":326},[2522],{"type":29,"value":2523},"    get",{"type":23,"tag":154,"props":2525,"children":2526},{"style":236},[2527],{"type":29,"value":810},{"type":23,"tag":154,"props":2529,"children":2530},{"style":478},[2531],{"type":29,"value":1262},{"type":23,"tag":154,"props":2533,"children":2534},{"style":172},[2535],{"type":29,"value":185},{"type":23,"tag":154,"props":2537,"children":2538},{"class":156,"line":232},[2539],{"type":23,"tag":154,"props":2540,"children":2541},{"style":478},[2542],{"type":29,"value":2543},"    set\n",{"type":23,"tag":154,"props":2545,"children":2546},{"class":156,"line":251},[2547],{"type":23,"tag":154,"props":2548,"children":2549},{"style":172},[2550],{"type":29,"value":461},{"type":23,"tag":154,"props":2552,"children":2553},{"class":156,"line":259},[2554,2558,2562,2566,2571,2575,2579],{"type":23,"tag":154,"props":2555,"children":2556},{"style":478},[2557],{"type":29,"value":1348},{"type":23,"tag":154,"props":2559,"children":2560},{"style":172},[2561],{"type":29,"value":175},{"type":23,"tag":154,"props":2563,"children":2564},{"style":478},[2565],{"type":29,"value":1376},{"type":23,"tag":154,"props":2567,"children":2568},{"style":236},[2569],{"type":29,"value":2570}," -=",{"type":23,"tag":154,"props":2572,"children":2573},{"style":478},[2574],{"type":29,"value":1386},{"type":23,"tag":154,"props":2576,"children":2577},{"style":172},[2578],{"type":29,"value":1187},{"type":23,"tag":154,"props":2580,"children":2581},{"style":1410},[2582],{"type":29,"value":2583},"  // ← 忘れがち\n",{"type":23,"tag":154,"props":2585,"children":2586},{"class":156,"line":293},[2587,2591,2595,2599],{"type":23,"tag":154,"props":2588,"children":2589},{"style":478},[2590],{"type":29,"value":1348},{"type":23,"tag":154,"props":2592,"children":2593},{"style":172},[2594],{"type":29,"value":442},{"type":23,"tag":154,"props":2596,"children":2597},{"style":478},[2598],{"type":29,"value":404},{"type":23,"tag":154,"props":2600,"children":2601},{"style":172},[2602],{"type":29,"value":185},{"type":23,"tag":154,"props":2604,"children":2605},{"class":156,"line":302},[2606,2610,2614,2618,2622,2626],{"type":23,"tag":154,"props":2607,"children":2608},{"style":478},[2609],{"type":29,"value":1348},{"type":23,"tag":154,"props":2611,"children":2612},{"style":172},[2613],{"type":29,"value":175},{"type":23,"tag":154,"props":2615,"children":2616},{"style":478},[2617],{"type":29,"value":1376},{"type":23,"tag":154,"props":2619,"children":2620},{"style":236},[2621],{"type":29,"value":1381},{"type":23,"tag":154,"props":2623,"children":2624},{"style":478},[2625],{"type":29,"value":1386},{"type":23,"tag":154,"props":2627,"children":2628},{"style":172},[2629],{"type":29,"value":185},{"type":23,"tag":154,"props":2631,"children":2632},{"class":156,"line":336},[2633],{"type":23,"tag":154,"props":2634,"children":2635},{"style":172},[2636],{"type":29,"value":568},{"type":23,"tag":154,"props":2638,"children":2639},{"class":156,"line":344},[2640],{"type":23,"tag":154,"props":2641,"children":2642},{"style":172},[2643],{"type":29,"value":693},{"type":23,"tag":31,"props":2645,"children":2646},{},[2647,2652],{"type":23,"tag":45,"props":2648,"children":2650},{"className":2649},[],[2651],{"type":29,"value":1376},{"type":29,"value":2653},"はC#のイベントの中でも特にリークしやすいパターンで、「古いインスタンスを参照しっぱなし」の状態を作りやすいです。Blazor WASMはアプリ全体で1つのプロセスなので、ここでリークするとセッション中にずっと残ります。",{"type":23,"tag":2655,"props":2656,"children":2657},"caution-box",{},[2658],{"type":23,"tag":31,"props":2659,"children":2660},{},[2661,2667,2669,2674],{"type":23,"tag":45,"props":2662,"children":2664},{"className":2663},[],[2665],{"type":29,"value":2666},"IDisposable",{"type":29,"value":2668},"の実装も本来は検討すべきですが、PICOMではViewModelの寿命がアプリ起動時〜終了時と長いため、デタッチはすべてセッター内で完結させて簡潔にしました。よりシビアな環境（コンポーネントが頻繁に作り直されるケース）では、",{"type":23,"tag":45,"props":2670,"children":2672},{"className":2671},[],[2673],{"type":29,"value":2666},{"type":29,"value":2675},"を明示的に実装した方が安全です。",{"type":23,"tag":24,"props":2677,"children":2679},{"id":2678},"まとめ",[2680],{"type":29,"value":2678},{"type":23,"tag":916,"props":2682,"children":2683},{},[2684,2694,2704,2709],{"type":23,"tag":920,"props":2685,"children":2686},{},[2687,2692],{"type":23,"tag":45,"props":2688,"children":2690},{"className":2689},[],[2691],{"type":29,"value":70},{"type":29,"value":2693},"は20行程度で書けるので、小規模アプリならMVVM Toolkitを避けて自前で書くのも選択肢",{"type":23,"tag":920,"props":2695,"children":2696},{},[2697,2702],{"type":23,"tag":45,"props":2698,"children":2700},{"className":2699},[],[2701],{"type":29,"value":78},{"type":29,"value":2703},"で初期スナップショットと編集中オブジェクトの差分をイベント通知に変換できる",{"type":23,"tag":920,"props":2705,"children":2706},{},[2707],{"type":29,"value":2708},"イベントは「差分フラグが変化した瞬間だけ」に絞ると、レンダリングループに優しい",{"type":23,"tag":920,"props":2710,"children":2711},{},[2712,2717],{"type":23,"tag":45,"props":2713,"children":2715},{"className":2714},[],[2716],{"type":29,"value":1376},{"type":29,"value":2718},"の購読解除は徹底する。忘れるとメモリリークの温床になる",{"type":23,"tag":2720,"props":2721,"children":2722},"style",{},[2723],{"type":29,"value":2724},"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":188,"depth":188,"links":2726},[2727,2728,2731,2734,2735,2736],{"id":26,"depth":188,"text":26},{"id":120,"depth":188,"text":123,"children":2729},[2730],{"id":876,"depth":222,"text":879},{"id":969,"depth":188,"text":972,"children":2732},[2733],{"id":1617,"depth":222,"text":1617},{"id":1995,"depth":188,"text":1998},{"id":2470,"depth":188,"text":2473},{"id":2678,"depth":188,"text":2678},"markdown","content:articles:tech:blazor:diff-detectable-object.md","content","articles/tech/blazor/diff-detectable-object.md","articles/tech/blazor/diff-detectable-object","md",1776181090615]