質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

2回答

1467閲覧

あるクラスのpublicメンバを呼び出す必要がない別クラスで、そのメンバの呼び出しを行わないことを明示したい

aki107205

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

1グッド

2クリップ

投稿2021/09/27 07:29

編集2021/09/27 09:33

実現したいこと

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の環境を使用しています。

dodox86👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2021/09/27 07:38

init が正解のクイズですか?
退会済みユーザー

退会済みユーザー

2021/09/27 08:36 編集

新規作成機能?ってのが良く分からないのできちんと書いてください。 1ソースで実行可能なサンプルにして貰えると検証しやすいです。
aki107205

2021/09/27 09:37

initについては完全に知りませんでした。質問に記載すべきだったのですがC#のバージョン問題で当環境では使用できないようです。教えていただきありがとうございます。 また動かせるコードを追記しました。ただこのコードが動くかどうかというよりも、実際の質問意図は「同アセンブリ内でこのクラスにはpubicで触らせたいけどこっちのクラスではダメ!」というのをC#の仕様で実現可能なのか、ということです。
guest

回答2

0

ベストアンサー

public set可能なプロパティを定義しつつも、public setを呼び出すつもりがない処理でそのクラスのインスタンスを扱うときには呼び出しをしないことをコード上明確にし、誤って呼び出したときにはコンパイルエラーとする良い方法

public set できるプロパティがあるという条件が付いた場合はそのような方法はありません。

set キーワードを使うのではなくプロパティを get-only プロパティ として定義してコンストラクタで初期化するか、C# 9.0 以降で使える init-only プロパティ を使ってください。

追記

XmlSerializer を使う場合は読み取り専用でも逆シリアル化できるのか気になり、ふと調べたら下記質問が見つかりました。
Force XML serialization to serialize readonly property

set アクセサーで NotSupportedException をスローさせることで触れないようにしているようなので XMLSerializer を使う場合はこれが現実的な解決策かもしれません。

ただし本質的な解決策か疑問に思ったのでふと調べたら次は公式のガイドラインが見つかりました。
シリアル化のガイドライン

XmlSerializer の代わりに DataContractSerializer でも XML のシリアル化・逆シリアル化ができるようです。

これを使う場合は

Data-MemberAttribute を持つすべてのプロパティに getter と setter を実装してください

とのことなので、get-only プロパティだと駄目かもしれませんがサンプルにもある通り private set で定義してコンストラクタ経由でなら逆シリアル化できると思います。

C#

1using System.Runtime.Serialization; 2 3[DataContract] 4public class Person 5{ 6 public Person(string firstName, string lastName) 7 { 8 LastName = lastName; 9 FirstName = firstName; 10 } 11 12 [DataMember] 13 public string LastName { get; private set; } 14 15 [DataMember] 16 public string FirstName { get; private set; } 17}

投稿2021/09/27 08:54

編集2021/09/27 16:00
BluOxy

総合スコア2663

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

BluOxy

2021/09/27 09:53

> 「同アセンブリ内でこのクラスにはpubicで触らせたいけどこっちのクラスではダメ!」というのをC#の仕様で実現可能なのか これはできないので、やるなら private set か get-only プロパティにするか init-only プロパティにするかで他のクラスから参照できないようにするのがシンプルだと思います。
aki107205

2021/09/27 09:59

ありがとうございます。「別のアプローチ」の節で記載の通りDataContractSerializerでprivate setにする方法にはたどり着いていたのですが、手間を考えて躊躇っていました。(自分で考えた他の方法よりはマシでしょうが・・・)ストレートにやる方法はないということを答えていただいてスッキリしました。
guest

0

C#8.0以上なら、アクセサに対してObsoluteが適用出来ます。
(今回のような用途で使うのが適切かどうかは判りません)

cs

1 [XmlRoot("myData")] 2 public class MyData 3 { 4 public MyData() { } 5 6 [XmlElement("data1")] 7 public string Data1 { get; [Obsolete("setしないでね!")]set; } //setするとwarning 8 9 [XmlElement("data2")] 10 public string Data2 { get; [Obsolete("set禁止!", true)]set; } //setするとerror 11 }

Obsoluteイメージ

別アセンブリにしていいなら、internal辺りでも何とかなるかもしれません。

投稿2021/09/27 09:47

編集2021/09/27 10:01
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aki107205

2021/09/27 10:01

ありがとうございます。少し想定していたものと違う気もしますが、他に簡単な方法が無さそうな以上、実用面では便利そうに感じました。(残念ながらC#バージョンのせいで使えないのですが)
退会済みユーザー

退会済みユーザー

2021/09/27 10:09

真面目にやろうとするならアナライザー作ったら出来るかもしれませんけど、専門外なんで深くは触れないでおきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問