実現したいこと
C#において、あるクラスにpublic set可能なプロパティを定義しつつも、public setを呼び出すつもりがない処理でそのクラスのインスタンスを扱うときには呼び出しをしないことをコード上明確にし、誤って呼び出したときにはコンパイルエラーとする良い方法はないでしょうか?
イメージはListが必要だが内容操作が不要なとき、IReadOnlyListとして受け渡すことで(無理やりキャストしない限り)エラーとなるといった手法を自ら定義したクラスで行いたい形です。
質問の背景
System.Xml.Serializationでシリアライズ/デシリアライズしているデータがあるのですが、
そのデータはxmlファイルから読み込む場合には読み込み時以外には触ることがなく、
新規作成する場合にも新規作成を行う機能だけが触れればいい内容です。
System.Xml.Serializationの仕様上setをpublicにしないといけないのと、
新規作成機能だけは自由にデータを弄らせたいこともあってset自体をprivateにするのは難しいと考えています。
ソースコード(イメージ)
C#
1[XmlRoot("myData")] 2public class MyData 3{ 4 public MyData() { } 5 6 [XmlElement("data1")] 7 public string Data1 { get; set; } 8 9 [XmlElement("data2")] 10 public string Data2 { get; set; } 11 12 .... 13} 14 15public class DataUser 16{ 17 public DataUser(MyData myData) //IReadOnlyMyDataを定義してそちらで受け渡す? 18 { 19 //MyDataの値を変更すべきではない処理 20 } 21}
動かせるソースコード例(追記)
C#
1using System; 2using System.Collections.Generic; 3using System.IO; 4using System.Linq; 5using System.Text; 6using System.Threading.Tasks; 7using System.Xml.Serialization; 8 9namespace TestApp 10{ 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 //新規データ作成(デスクトップ) 16 string xmlFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "myData.xml"); 17 var generator = new MyDataGenerator(); 18 generator.GenerateAndSave(xmlFile); 19 20 //ファイルからロード 21 MyData myData = MyDataLoader.Load(xmlFile); 22 23 //利用(編集しない) 24 var user = new MyDataUser(); 25 user.UseData(myData); 26 27 Console.ReadLine(); 28 } 29 } 30 31 [XmlRoot("myData")] 32 public class MyData 33 { 34 public MyData() { } 35 36 [XmlElement("data1")] 37 public string Data1 { get; set; } 38 39 [XmlElement("data2")] 40 public string Data2 { get; set; } 41 } 42 43 44 class MyDataGenerator 45 { 46 public void GenerateAndSave(string path) 47 { 48 var myData = new MyData(); 49 myData.Data1 = "abcde"; //実際はユーザー入力などを受け取って設定する 50 myData.Data2 = "fghij"; 51 52 var xmlSerializer1 = new XmlSerializer(typeof(MyData)); 53 using (var streamWriter = new StreamWriter(path, false, Encoding.UTF8)) 54 { 55 xmlSerializer1.Serialize(streamWriter, myData); 56 streamWriter.Flush(); 57 } 58 } 59 } 60 61 static class MyDataLoader 62 { 63 public static MyData Load(string path) 64 { 65 var xmlSerializer = new XmlSerializer(typeof(MyData)); 66 MyData myData; 67 using (var streamReader = new StreamReader(path, Encoding.UTF8)) 68 using (var xmlReader = System.Xml.XmlReader.Create(streamReader)) 69 { 70 myData = (MyData)xmlSerializer.Deserialize(xmlReader); 71 } 72 73 return myData; 74 } 75 } 76 77 class MyDataUser 78 { 79 public void UseData(MyData myData) 80 { 81 myData.Data1 = "hogehoge"; //コンパイルを通したくない 82 Console.WriteLine(myData.Data1); 83 Console.WriteLine(myData.Data2); 84 } 85 } 86}
自分で考えた実現方法
- 全てのプロパティをgetのみとしたIReadOnlyMyDataインターフェースを定義し、MyDataに実装させる
- MyDataをメンバーに持ち、getのみを仲介するReadOnlyMyData(readOnlyMyData.Data1が呼ばれると内部の_myData.Data1が返る)クラスを定義する
どちらもMyDataに存在するプロパティを全て同名でインターフェースかクラスに作成しなければならず、Data側が変わったときの二重管理が非常に大変そうなので他にいい方法があるのではないかと思っています。
別のアプローチについて
DataContractSerializerを使ってsetをprivateにし、新規作成はコンストラクタ引数で行うようにすれば背景の問題は解消しそうな気がしていますが、xmlのデータ順が縛られる上、データクラスへの変更が大きいため二の足を踏んでいます。
また背景に囚われずに一般論として質問のようなことが出来るか/どうやって行うか興味があるため質問させていただきました。
補足情報(追記)
C# 7.2 / .NET4.6.1の環境を使用しています。
回答2件
あなたの回答
tips
プレビュー