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

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

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

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

Q&A

解決済

2回答

8502閲覧

【C#】メッセージクラスにユニークなIDをつけたい

syogakusya

総合スコア67

C#

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

0グッド

0クリップ

投稿2016/09/05 11:29

##前提
イベントビューアに出力するログを管理したく、
メッセージ・ID・メッセージの種類(通知/エラー)をひとまとめにして合理的に管理したいです。
悩んでいたところ以下のようなコードを教えていただくことが出来ました。

C#

1public enum MessageLevel 2{ 3 Info, 4 Error 5} 6 7// メッセージ本体の抽象構造定義 8 9public abstract class Message 10{ 11 public abstract int Id { get; } 12 13 public abstract string Text { get; } 14 15 public abstract MessageLevel Level { get; } 16} 17 18public abstract class Info : Message 19{ 20 public override MessageLevel Level => MessageLevel.Info; 21} 22 23public abstract class Error : Message 24{ 25 public override MessageLevel Level => MessageLevel.Error; 26} 27 28 29// 共通メッセージ定義 30 31public class TaskDone : Info 32{ 33 public override int Id => 1; 34 35 public override string Text => "all tasks are done"; 36 37} 38 39public class CommandLineArgumentsError : Error 40{ 41 public override int Id => 2; 42 43 public override string Text => "need some command-line arguments"; 44} 45 46public class SpecialError : Error 47{ 48 public override int Id => 3; 49 50 public override string Text => "failed to backup log files"; 51} 52 53 54// ================================================================ 55// Project1 アセンブリ内 56 57// 追加メッセージ 58 59public class Project1HogeError : Error 60{ 61 public override int Id => 4; 62 63 public override string Text => "project1 local error"; 64} 65 66 67// ================================================================ 68// クライアントコード 69 70class Test 71{ 72 public void Main() 73 { 74 var message = new TaskDone(); 75 76 System.Console.WriteLine($"ID:{ message.Id }, Level:{ message.Level }, Content:{ message.Text }"); 77 } 78}

このコードは私が最初に書いたものより格段に分かりやすくなっているのですが、
個人的な都合により、いくつか課題があります。

##課題

1... IDをユニークにしやすいような仕組みがないこと

イベントログに出力するIDとメッセージは1対1にしたいため、
各クラスのIdプロパティにハードコーディングでIDを設定するよりは
簡単で安全な方法で各メッセージのIDを設定したいです。

2... IDを連番にしやすいような仕組みがないこと

1つ目の理由に近いのですが、IDは連番にしたいため
やはりIDはより簡単な方法で設定したいです。

私が最初に書いたコードは、
目も当てられないほど酷いコードだったのですが、
各メッセージを列挙体のメンバーと1対1で定義していたため、
IDをユニークにしたり連番にしたりすることは簡単でした。
列挙体なら各メンバーはデフォルトで一意かつ連番な整数値を持つからです。
##やりたいこと
前述のコードの良いところを利用しながら、IDの管理も合理的にできるようなコードを書きたいです。
アドバイスをよろしくお願いいたします。

##補足
この質問はひとつ前の質問の続きになっています。
見ても意味はないかもしれませんが、何か気になる点が御座いましたら御覧ください。
当該の質問につきましても、回答を受付中です。

ひとつ前の質問

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

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

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

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

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

guest

回答2

0

ベストアンサー

こんにちは。

  • Idは一意かつ連番とする
  • Message種別毎にIdの範囲を設定する

という2つのみを加えて実現するように書いてみました。

csharp

1public abstract class Message 2{ 3 public int Id { get; } // ベースクラスがIdの実体を持つように 4 5 public abstract string Text { get; } 6 7 public abstract MessageLevel Level { get; } 8 9 public Message(int id) 10 { 11 this.Id = id; 12 } 13} 14 15public abstract class Info : Message 16{ 17 private static int id = 10000; // 初期値 18 19 public override MessageLevel Level => MessageLevel.Info; 20 21 public Info() : base(Interlocked.Increment(ref id)) // newする度にインクリメントする 22 { } 23} 24 25public abstract class Error : Message 26{ 27 private static int id = 20000; 28 29 public override MessageLevel Level => MessageLevel.Error; 30 31 public Error() : base(Interlocked.Increment(ref id)) 32 { } 33} 34 35 36// 共通メッセージ定義 37 38public class TaskDone : Info 39{ 40 public static Message Instance { get; } = new TaskDone(); // シングルトンインスタンス 41 42 public override string Text => "all tasks are done"; 43 44 private TaskDone() // コンストラクタを非公開に 45 { } 46} 47 48public class CommandLineArgumentsError : Error 49{ 50 public static Message Instance { get; } = new CommandLineArgumentsError(); 51 52 public override string Text => "need some command-line arguments"; 53 54 private CommandLineArgumentsError() 55 { } 56} 57 58public class SpecialError : Error 59{ 60 public static Message Instance { get; } = new SpecialError(); 61 62 public override string Text => "failed to backup log files"; 63 64 private SpecialError() 65 { } 66} 67 68 69class Test 70{ 71 public void Main() 72 { 73 var message = TaskDone.Instance; // シングルトンインスタンスを取得 74 75 System.Console.WriteLine($"ID:{ message.Id }, Level:{ message.Level }, Content:{ message.Text }"); 76 } 77}

各Messageをnewする度にIdをインクリメントし、各Messageをシングルトンとすることで、「各Messageに『一意』かつ『連番』なIdを自動で設定する」ことを実現しました。
問題があるとすると、どのMessageに対してどのIdが振られるかが決まらないため、「初回の実行時にId1だったあのメッセージは、次の実行時にはId2に割り当てられているかもしれない」ということです。
各回実行毎の一意性は確保していますが、同一性が保証できないという話になりますね。
質問の要件には含まれていなかったのでこの問題について完全に無視していますが、こちらも要件に含む場合は、ハードコーディング以外の道は殆ど残っていないと思います。
他には、全てのMessageを最初にまとめて順番にインスタンス化してリストなどに突っ込んでしまい、割り振られる順番を固定してしまうという方法もなくはないですね。ほぼハードコーディングのようなものですが。

投稿2016/09/06 06:57

tamoto

総合スコア4228

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

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

syogakusya

2016/09/06 12:08

回答ありがとうございます。 IDの同一性を要件に含むことを失念していました。 言及いただきありがとうございます。 御推察のとおり、仕様書にIDと内容の対応を載せたりといったことがあるので、 IDとメッセージの同一性は要件に含めるべきでした。 そうなるとやはりハードコーディングになってしまうのですね。 列挙体を継承さえできればと思わずにはいられません。
syogakusya

2016/09/06 14:20

アプリケーションの設計について真剣に考えてみましたところ、 そもそも共有メッセージと固有のメッセージは連番にしてはならないことが分かりました。 共有メッセージを増やすことで各プロジェクトの固有メッセージのIDがずれていってしまうのでは、メッセージのIDに同一性があるとは言えないからです。 固有メッセージは1~5000、共有メッセージは5001~10000といった形でもう一度考え直してみます。 その際、頂いたソースを参考にしようと思います。 お付き合いいただきありがとうございました。
guest

0

こんにちは。

たぶん、シングルトンを作って、その中でIDをインクリメントすれば行けると思います。

例えば、上記リンク先のシングルトンに、int id;を追加(コンストラクタで0クリア)。
メソッドint getId() { return id++; }を追加ですね。
そして、各メッセージのidをstaticにして、上記値をセットするイメージです。

すいません。最近、C#を触ってないのでコードをパッと書けません。

投稿2016/09/05 12:27

Chironian

総合スコア23272

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

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

syogakusya

2016/09/05 15:54

インスタンスをnewするたびに1ずつIDを増やすことはできましたが、 クラスに紐づけてIDを設定する方法は分かりませんでした。 また、newした順にIDがつけられていくため、同じメッセージに対し同じIDが割り当てられることが保証できませんでした。
Chironian

2016/09/05 16:59

> クラスに紐づけてIDを設定する方法は分かりませんでした。 staticフィールドはご存知ないですか? 上記のリンク先を見て頂ければ解説されてます。 staticフィールドの初期化は上記のリンク先の「静的コンストラクター」でいけると思います。 静的コントラクタでid=シングルトンのgetInstance().getId();です。 基底クラスから派生クラスのstaticフィールドをアクセスしたい場合は、仮想関数で各クラスのstaticフィールド(id)を返却すれば良いです。 > 同じメッセージに対し同じIDが割り当てられることが保証できませんでした。 この要件は満たせないかも知れません。アプリ起動後終了するまでは変化しない筈です。 しかし、C#の厳密な仕様を知りませんが、静的コンストラクターが呼び出される順序が不変であることは保証されていないだろうと思います。つまり、アプリを再起動した時に変化しないことは保証できないと思います。 代替案ですが、各メッセージをシングルトン化し、コンストラクタで「直前」のメッセージをgetInstance()して、そのIDを+1し自分のIDとすることが考えられます。 基底クラスからそれを取り出す際は冒頭で述べた考え方で行けると思います。 その仮想関数で自分をgetInstance()することをお忘れなく。(はじめての呼び出し時に、このgetnstance()でコンストラクタを起動し、IDを設定します。) なお、同じメッセージを複数のメッセージが参照するとIDが重複するので要注意です。 コードを示せないので分かり難いと思います。申し訳ないです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問