@Guffaの回答に似たようなことをしましたが、プロパティの「フック」ラッパーを保持するオブジェクトの代わりに、ラムダを使用して、元の値型を使用してプロパティを取得/設定しました。
マスタークラス:
class MasterClass
{
Dictionary<string, HookObj> dict = new Dictionary<string, HookObj>();
//Data came in from an external source, see if we know what variable to assign the value to
public void receiveData(string key, string value)
{
if (dict.ContainsKey(key))
assignVal(dict[key], value);
else
throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
}
//Cast the value-string to the proper type and assign it
private void assignVal(HookObj hookobj, string value)
{
try
{
if (hookobj.theType == typeof(string))
hookobj.SetValue(value);
else if (hookobj.theType == typeof(int))
hookobj.SetValue(Int32.Parse(value));
else if (hookobj.theType == typeof(float))
hookobj.SetValue(float.Parse(value));
else
throw new NotImplementedException();
}
catch (RuntimeBinderException ex) { throw new NotImplementedException("", ex); }
catch (System.FormatException ex) { throw new NotImplementedException("", ex); }
}
protected void newHookAndAdd<T>(Action<T> setter, Func<T> getter, string name)
{
HookObj hook = new HookObj<T>(setter, getter);
dict.Add(name, hook);
}
}
HookObj
public class HookObj<T> : HookObj
{
public Action<T> setMethod { get; private set; }
public Func<T> getMethod { get; private set; }
public HookObj(Action<T> setMethod, Func<T> getMethod)
: base(typeof(T))
{
this.setMethod = setMethod;
this.getMethod = getMethod;
}
public override void SetValue(object value)
{
setMethod((T)value);
}
public override object GetValue()
{
return getMethod();
}
}
public abstract class HookObj
{
public Type theType { get; private set; }
public HookObj(Type theType)
{
this.theType = theType;
}
public abstract void SetValue(object value);
public abstract object GetValue();
}
次に、MasterClassのサブクラス:
class Attachvariable : MasterClass
{
public int UserID { get; set; }
public string Position { get; set; }
public Attachvariable()
{
hookMethod();
}
protected void hookMethod()
{
newHookAndAdd(value => UserID = value, () => UserID, "userID");
newHookAndAdd(value => Position = value, () => Position, "position");
}
}
パーサーを提供するようにフック登録者を設定することもできます。
class YesNoVariable : MasterClass
{
public bool YesNo { get; set; }
public YesNoVariable()
{
hookMethod();
}
protected void hookMethod()
{
newHookAndAdd(value => YesNo = value, () => YesNo, "yesno", (input) => input == "yes");
}
}
パーサーをオプションのパラメーターとしてベースハンドラーに追加する動作は実行しませんでした。この段階では些細なことなので、これはお任せします。基本的に、パーサーが割り当てられているassignValue
かどうかを確認し、割り当てられている場合はそれを使用します。HookObj
それ以外の場合は、すでに持っているのと同じ動きをします。
実際、私はすべてのフックにパーサーを使用させ、特定IntHookObj
の、FloatHookObj
などを持たせます。このnewHookAndAdd
方法は基本的に工場です。ユーザーが独自のパーサーを提供する場合、HookObj
そのカスタムパーサーを使用してを作成します。T
がint/float / stringの場合、既知の実装がインスタンス化されるHookObj
ため、すべての解析ロジックを割り当てプロセスと混同することはありません。
編集:カスタムパーサーを使用して実装をまとめ、if / elseif /elseifタイプチェックを完全に破棄することになりました:
フック基本クラス
public abstract class HookObj<T> : HookObj
{
public Action<T> setMethod { get; private set; }
public Func<T> getMethod { get; private set; }
protected HookObj()
: base(typeof(T))
{
}
public void SetSetter(Action<T> setMethod)
{
this.setMethod = setMethod;
}
public void SetGetter(Func<T> getMethod)
{
this.getMethod = getMethod;
}
protected abstract T Parse(string value);
public override void SetValue(string value)
{
T parsedValue = Parse(value);
setMethod(parsedValue);
}
public override object GetValue()
{
return getMethod();
}
}
public abstract class HookObj
{
public Type theType { get; private set; }
public HookObj(Type theType)
{
this.theType = theType;
}
public abstract void SetValue(string value);
public abstract object GetValue();
}
標準フックの実装
public class StringHook : HookObj<string>
{
protected override string Parse(string value)
{
return value;
}
}
public class IntHook : HookObj<int>
{
protected override int Parse(string value)
{
return Int32.Parse(value);
}
}
public class FloatHook : HookObj<float>
{
protected override float Parse(string value)
{
return float.Parse(value);
}
}
カスタムフックパーサーハンドラー
public class CustomHook<T> : HookObj<T>
{
public Func<string, T> inputParser { get; private set; }
public CustomHook(Func<string, T> parser)
{
if (parser == null)
throw new ArgumentNullException("parser");
this.inputParser = parser;
}
protected override T Parse(string value)
{
return inputParser(value);
}
}
マスタークラス:
class MasterClass
{
Dictionary<string, HookObj> dict = new Dictionary<string, HookObj>();
//Data came in from an external source, see if we know what variable to assign the value to
public void receiveData(string key, string value)
{
if (dict.ContainsKey(key))
assignVal(dict[key], value);
else
throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
}
//Cast the value-string to the proper type and assign it
private void assignVal(HookObj hookobj, string value)
{
hookobj.SetValue(value);
}
protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, Func<string, T> inputParser)
{
var hook = new CustomHook<T>(inputParser);
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty(Action<string> setter, Func<string> getter, string name)
{
var hook = new StringHook();
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty(Action<int> setter, Func<int> getter, string name)
{
var hook = new IntHook();
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty(Action<float> setter, Func<float> getter, string name)
{
var hook = new FloatHook();
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
}
これで、MasterClassは少しの作業を使用できます。具体的には、 ( newHookAndAddを置き換えたRegisterProperty
)メソッドは作業を複製しており、サポートする「標準」フックタイプごとにエントリを追加する必要があると思います。より良い方法があると確信していますが、今のところ、どのフックが使用されているかを気にせずに「Attachvariable」サブクラスを提供し、ネイティブでサポートされているタイプとそのタイプセーフを認識しています。ネイティブにサポートされていないタイプは、タイプセーフなパーサーを提供する必要があります。
変数のサンプルを添付:
class Attachvariable : MasterClass
{
public int UserID { get; set; }
public string Position { get; set; }
public bool YesNo { get; set; }
public bool ProperBoolean { get; set; }
public Attachvariable()
{
RegisterProperties();
}
protected void RegisterProperties()
{
RegisterProperty(value => UserID = value, () => UserID, "userID");
RegisterProperty(value => Position = value, () => Position, "position");
RegisterProperty(value => YesNo = value, () => YesNo, "yesno", (input) => input == "yes");
RegisterProperty(value => ProperBoolean = value, () => ProperBoolean, "ProperBoolean", (input) => Boolean.Parse(input));
}
}
およびプロパティはネイティブstring
でint
サポートされています。RegisterProperty
彼らは彼らのタイプに結びついた過負荷にぶつかった。ただし、bool
タイプはネイティブでサポートされていないため、独自の解析ロジックを提供します。「yesno」は、文字列が「yes」と等しいことを確認するだけです。「ProperBoolean」は標準を実行しますBoolean.Parse
。使用法は次のようになります。
Attachvariable obj = new Attachvariable();
obj.receiveData("userID", "9001");
obj.receiveData("position", "Hello World!");
obj.receiveData("yesno", "yes");
obj.receiveData("ProperBoolean", "True");
Console.WriteLine(obj.UserID); //9001
Console.WriteLine(obj.Position); //"Hello World!"
Console.WriteLine(obj.YesNo); //True
Console.WriteLine(obj.ProperBoolean); //True
obj.receiveData("yesno", "something else!");
Console.WriteLine(obj.YesNo); //False
obj.receiveData("ProperBoolean", "Invalid Boolean!"); //throws exception on `Boolean.Parse` step as intended
「標準」フックをCustomHookから継承し、解析メソッドを渡すことを考えましたが、この方法で、より複雑な解析ロジックを持つ可能性のある新しい標準フックを作成できるため、読み取り/実装が少し簡単になります。とにかく、私はこれを泡立てて、本番環境で使用する場合は、クリーニング/改善/テストにもっと時間がかかります。
biiiiiiiig plusは、これにより、個々のフックタイプの実装をユニットテストするのが非常に簡単になります。おそらく、単体テストを簡単にするためにリファクタリングすることができます(今ではかなり簡単ですが)、フックの作成者を別のファクトリ/ビルダーに移動することもできます。MasterClass
編集:ヘック、今theType
はベースのフィールドを捨てて、HookObj
それをインターフェースに置き換えます:
public interface IHookObj
{
void SetValue(string value);
object GetValue();
}
変更を全体に伝播できます(HookObj<T>
渡されるベースコンストラクターを呼び出さなくなりtypeof(T)
、インターフェイスMasterClass
に関連付けられるようになりましたIHookObj
)。これは、解析など、必要なロジックを使用するフックをさらに簡単に定義でき、テストがさらに簡単になることを意味します。
編集:ええ、これはAPIのサードパーティコンシューマーがアプリケーション全体で再利用できる独自のフックを提供できる実装例です。どこでもオブジェクトが使用されている場合はPerson
、単一のフックを定義するだけで、再利用できます。
class SomeCustomUsage : MasterClass
{
public Person SomeoneUnimportant { get; set; }
public SomeCustomUsage()
{
RegisterProperty(value => SomeoneUnimportant = value, () => SomeoneUnimportant, "SomeoneUnimportant", new PersonHook());
}
}
彼らのPersonHook
存在とともに:
public class PersonHook : HookObj<Person>
{
protected override Person Parse(string value)
{
string[] parts = value.Split(',');
var person = new Person(parts[0], parts[1]);
if (person.FirstName == "Bob")
throw new Exception("You have a silly name and I don't like you.");
if (String.IsNullOrWhiteSpace(person.FirstName))
throw new Exception("No first name provided.");
if (String.IsNullOrWhiteSpace(person.LastName))
throw new Exception("No last name provided.");
return person;
}
}
HookObj<T>
オーバーロードを提供すると、RegisterProperty
すべてのオーバーロードがコードの重複が少なく、より単純なものに折りたたまれます(ただし、まだ完全には正しく感じられません)。
protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, HookObj<T> hook)
{
hook.SetSetter(setter);
hook.SetGetter(getter);
dict.Add(name, hook);
}
protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, Func<string, T> inputParser)
{
var hook = new CustomHook<T>(inputParser);
RegisterProperty(setter, getter, name, hook);
}
protected void RegisterProperty(Action<string> setter, Func<string> getter, string name)
{
var hook = new StringHook();
RegisterProperty(setter, getter, name, hook);
}
protected void RegisterProperty(Action<int> setter, Func<int> getter, string name)
{
var hook = new IntHook();
RegisterProperty(setter, getter, name, hook);
}
protected void RegisterProperty(Action<float> setter, Func<float> getter, string name)
{
var hook = new FloatHook();
RegisterProperty(setter, getter, name, hook);
}
結果は次のようになります。
SomeCustomUsage customObj = new SomeCustomUsage();
customObj.receiveData("SomeoneUnimportant", "John,Doe");
Console.WriteLine(customObj.SomeoneUnimportant.LastName + ", " + customObj.SomeoneUnimportant.FirstName); //Doe, John
customObj.receiveData("SomeoneUnimportant", "Bob,Marley"); //exception: "You have a silly name and I don't like you."
customObj.receiveData("SomeoneUnimportant", ",Doe"); //exception: "No first name provided."
customObj.receiveData("SomeoneUnimportant", "John,"); //exception: "No last name provided."