自動実装プロパティは簡潔だけど応用が効かない。だが手動だと冗長すぎて使う気になれない。
成果物
コード
class Main { public void Run() { var h = new Human("NAME"); Console.WriteLine($"{h.Name}"); } } class Human { public string Name { get; } public Human(string name) => Name = name; }
set
を用意していないのに代入するとエラー。
class Human { public string Name { get; } public void SetDefault() => Name = "Default"; // error CS0200 }
シリアライズ
public class Human { public string Name { get; set; } [System.Xml.Serialization.XmlIgnore] public int Id { get; set; } public Human() {} public Human(string name) => Name = name; }
シリアル化するためには以下のような制約がある。
class
をpublic
にするpublic
な引数なしコンストラクタを作る- プロパティを
public
にしてset
を与える
失敗ログ
実行時エラー。引数なしコンストラクタが必要。
System.InvalidOperationException: Concept.Property.Lesson3.Human cannot be serialized because it does not have a parameterless constructor.
実行時エラー。public
でないとダメ。
System.InvalidOperationException: Concept.Property.Lesson3.Human is inaccessible due to its protection level. Only public types can be processed.
public class Human { public string Name { get; set; } [System.Xml.Serialization.XmlIgnore] public int Id { get; set; } public Human() {} public Human(string name) => Name = name; }
出た。けどId
まで出ちゃってる。NonSerialized
<?xml version="1.0"?> <Human xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Name>NAME</Name> <Id>0</Id> </Human>
よく見ると[field:NonSerialized]
はフィールドに対してのみ有効らしい。ならなぜ公式は以下のようなコードを書いたんだ? プロパティに対して使っている。まるで可能であるかのように…。
[field:NonSerialized] public int Id { get; set; }
シリアル化しない方法は以下。XmlIgnore属性を使う。
[System.Xml.Serialization.XmlIgnore] public int Id { get; set; }
Humanをシリアライズする例は以下。
class Main { public void Run() { var h = new Human("NAME"); Write(h); } private void Write(Human h) { System.Xml.Serialization.XmlSerializer writer = new System.Xml.Serialization.XmlSerializer(h.GetType()); using (System.IO.FileStream file = System.IO.File.Create("Human.xml")) { writer.Serialize(file, h); } } }
Human.xml
<?xml version="1.0"?> <Human xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Name>NAME</Name> </Human>
INotifyPropertyChanged
値が代入されたときにイベントが発生するヤツ。MVVM。
class Main { public void Run() { var h = new Human("NAME"); Console.WriteLine($"{h.Name}"); } } public class Human : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void RaiseRenamed([System.Runtime.CompilerServices.CallerMemberName]string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); private string name; public string Name { get => name; set { name = value; RaiseRenamed(); } } public Human() { PropertyChanged += OnPropertyChanged; } public Human(string name) { PropertyChanged += OnPropertyChanged; Name = name; } ~Human() { PropertyChanged -= OnPropertyChanged; } private void OnPropertyChanged(object sender, PropertyChangedEventArgs args) { Console.WriteLine($"値がセットされた!: {args}"); } }
値がセットされた!: System.ComponentModel.PropertyChangedEventArgs NAME
PropertyChanged
メンバは継承せねばならない。名前も固定。変えたらエラー……。
error CS0535: 'Human' does not implement interface member 'INotifyPropertyChanged.PropertyChanged'
妄想
妄想
上記のコードは汚い。もっと簡潔に書きたい。以下みたいに。残念ながら動作しない。
class Human { public string Name { get; set { Name = value; raise; } raise { // sender, args の引数が使える Console.WriteLine($"値がセットされた!: {sender}, {args}"); } } }
Nameプロパティがセットされたときのイベントを、クラス内の別の場所で呼び出したいときは以下。
class Human { public void Some() { raise Name; // PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))) } }
外部からイベントハンドラを設定するなら以下。
var h = new Human();
h.Changed.Name += (sender, args) => { 独自イベント処理; };
または以下でも可。
h.Changed["Name"] += (sender, args) => { 独自イベント処理; };
C#8.0との違いは以下。
- 自動実装プロパティの改善
- フィールド宣言不要。プロパティ名
Name
をそのまま使って代入可 get
だけセミコロン;
で省略しつつ、set
は{}
で実装できる
- フィールド宣言不要。プロパティ名
Invoke
略記- プロパティ内の
raise;
キーワードはPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(プロパティ名)))
を意味する
- プロパティ内の
- イベント時のメソッド定義
- class内
raise プロパティ名(...) => {...}
- class外
human.Changed.Name += (sender, args) => {...}
- class内
さらに短縮したい。+
/-
(<
/>
)でコードの先頭または最後にraise;
するよう指定できたら。
public string Name { get; set+; }
public string Name { get; set-; }
public void Some() raise+ => { ... } public void Some() raise- => { ... } public void Some() raise => { ... }
public void Some() + => { ... } public void Some() - => { ... }
つまり以下のようにするとName
に値がセットされたとき、raise =>
で定義したメソッドが実行される。
public string Name { get; set-; raise => Console.WriteLine($"値がセットされた!: {sender}, {args}");}
raise
も;
で省略できる。このときはConsole.WriteLine($"Raise: {sender}, {args}");
のような処理をデフォルトとする。
public string Name { get; set-; raise; }
+
/-
があればraise;
も省略できる。
public string Name { get; set-; }
これだけ縮められたら嬉しいのになぁ。
所感
プロパティでないところで長くなってしまった。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13 ※
- bash 4.4.12(1)-release ※
- SQLite 3.29.0 ※
- C# dotnet 3.0.100 ※
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux