Create a gist now

Instantly share code, notes, and snippets.

anonymous /IS2017erAC_1224.txt Secret
Created Dec 24, 2016

【C#】Dictionary<key, object>なセーブデータからキャストなしの一行で例外処理を入れつつ好きな型のデータを取り出す話
りじちょーです。代筆です。
この記事はIS17er Advent Calendar(http://www.adventar.org/calendars/1632)の24日目の記事です。
このGistって機能使うの初めてなので何ができるのか全く分からない。テキストファイルを作ればいいんだろうか。
IS17er、ゲーム制作民やUnity民が少ないのであまり需要のなさそうな記事です。
めんどくさがり屋が作ったセーブデータの話。
希望1. データを一括でまとめておけて、キーワードで出し入れできるセーブデータが作りたい。
希望2. どんな型でも受け入れてほしい。
希望3. Getで例外が発生した場合の戻り値を場合によって変えたい。
希望4. キャストめんどくさい。見りゃ分かるでしょ推定してよ。
そんな感じのわがままな思考のもと、セーブデータクラス(SaveData)を設計してみました。
まずデータ本体はDictionary<key, object>型です。型によらずキーと一緒に突っ込める構造が他に思いつかなかった。
keyをstringで管理するとスペルミスが多発するので、こちらは列挙体にします。
セーブデータに入れる内容が増えたらここに項目を追加していきます。
//------------------------
public enum Key
{
Item1,
Item2,
//...
}
public Dictionary<Key, object> Data = new Dictionary<Key, object>();
public void Set(Key key, object obj) => Data[key] = obj;
//------------------------
これで希望1, 2はできたはず。
Set()にはswitch(key)などで適当な処理を入れたりします。
で、残りの3, 4を同時に実現するために次のようなGet()を作りました。
要は、使用時に例外処理を指定することで、キャストせずとも型推定ができるようにしてやればいいのです。
//------------------------
public delegate T ReturnWhenException<T>();
public T Get<T>(Key key, ReturnWhenException<T> ret = null)
{
//指定したkeyのvalueがあり、その型がTならそのまま返す
if (Data[key] != null && Data[key] is T)
return (T)Data[key];
else
{
//そうでなければ例外処理を行う。
//例外処理が指定されていなければTの既定値を返す。
if(ret == null)
{
return default(T);
}
else
{
return ret();
}
}
}
//------------------------
例外処理を指定した場合、その戻り値でReturnWhenException<T>の<T>が指定されるため、
Get<T>()はGet()と省略できます。
例えばKey.Item1に対応するデータがint型なのであれば、
SaveData型のインスタンスsvdがあったとして、
//------------------------
var a = svd.Get(SaveData.Key.Item1, () => 0);
//------------------------
等と書くことで、明示的なキャストなしに一行で例外処理を含めたデータの取得が可能です。
なお例外がdefault(T)値でよい場合は第二引数は省略できますが、Getの型指定が必要になります。
//------------------------
var a = svd.Get<int>(SaveData.Key.Item1);
//------------------------
これも省略できないかと考えてはみたものの、思いつきませんでした。
もっとも、可読性の観点から言えば式のどこかに型がわかる部分があるべきですね。
また、よく使う例外処理(セーブデータであれば各値の初期化処理)は、毎回ラムダ式で書くよりはクラス内メソッドにしてしまったほうが楽でしょう。
例えば
//------------------------
public static int Init_Item1(){
//初期化処理
}
//------------------------
とでも入れておけば、
//------------------------
int a = svd.Get(SaveData.Key.Item1, SaveData.Init_Item1);
//------------------------
と書けます(aの宣言はvarのままでもいいですが、見た目上型が分からなくなるためあえて明示しています)。
さらにSaveDataクラス内で使う分には、
//------------------------
int a = svd.Get(Key.Item1, Init_Item1);
//------------------------
すっきり。
必要なデータが決まっている、あるいはkeyをstring等にしてしまうなら、ライブラリにしてusing staticするのもいいかもしれません。
プログラミングは完全に独学なので、何か禁忌に触れていないかビクビクしつつ、りじちょーでした。
public class SaveData
{
public enum Key
{
Item1,
Item2,
}
public Dictionary<Key, object> Data = new Dictionary<Key, object>();
public SaveData()
{
Data = new Dictionary<Key, object>();
}
public SaveData(SaveData sd):
{
Data = new Dictionary<Key, object>(sd.Data);
}
public void Set(Key key, object obj) => Data[key] = obj;
public delegate T ReturnWhenException<T>();
public T Get<T>(Key key, ReturnWhenException<T> ret = null)
{
if (Data[key] != null && Data[key] is T)
return (T)Data[key];
else
{
if(ret == null)
{
return default(T);
}
else
{
return ret();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment