我有一个对象的通用字典,其中键的类型为Type
:
public class DynamicObject : IDictionary<Type, object>
这个想法是这个对象在基于插件的架构中共享,因此一个类型(可以驻留在一个插件.dll中)被用作一个键,以防止插件之间的冲突。此类型还用于存储有关该字段的元数据(例如该字段的描述或该字段的实际类型)。
目前,插件需要使用与此类似的代码设置此对象上的字段值:
DynamicObject someObject;
string someValue;
someObject[typeof(UsernameField)] = someValue;
唯一的问题是这不是类型安全的 - 即使类型UsernameField
知道它所期望的值的确切类型(例如int
或在这种情况下{ {1}}),此处提供的值只是作为对象键入。我想使用泛型来设置/获取属性类型安全,但我不确定如何。到目前为止,我提出的最好的是:
string
这是类型安全的,但这意味着我的每个字段类型(例如// Field is a base class for all types used as keys on DynamicObject
[Description("Username of the user")]
public class UsernameField : Field
{
public static void Set(DynamicObject obj, string value)
{
obj[typeof(UsernameField)] = someValue;
}
}
// To set fields
UsernameField.Set(obj, someValue);
)都具有几乎相同的静态UsernameField
方法。
如何在不使用每种字段类型的方法的情况下,以这种方式对值进行类型安全访问?
顺便说一下,使用Set
这样的关键是一个好主意,还是有一些我还不知道的隐藏陷阱?
答案 0 :(得分:2)
定义所有插件必须实现的接口:
public interface IPlugin {
string Name { get; }
string Author { get; }
string Description { get; }
void Init();
}
然后使用Dictionary<Type, IPlugIn>
。
通常,接口在单独的dll中声明(例如“MyCompany.MyProject.PlugIns.Contracts.dll”)。
诀窍是在Field
和UsernameField
之间使用通用Set
方法创建泛型类。 Field
不是通用的,这使得所有字段类型都可以分配给它。如果它被声明为Field<T>
,则情况并非如此。
public abstract class Field
{
}
public abstract class GenericField<T> : Field
{
public void Set(DynamicObject obj, T value)
{
obj[this.GetType()] = value;
}
}
public class UsernameField : GenericField<string>
{
#region Singleton Pattern
public static readonly UsernameField Instance = new UsernameField();
private UsernameField() { }
#endregion
}
由于我们需要在GetType
方法中调用Set
,因此我们无法将其声明为static
。因此,我使用了单例模式。
现在,我们可以以类型安全的方式设置字段:
UsernameField.Instance.Set(obj, "Joe");
附加1:
由于现在字段是单例,您可以使用字段作为字典的键而不是字体。
public class DynamicObject : IDictionary<Field, object> { }
Set
将成为:
public void Set(DynamicObject obj, T value)
{
obj[this] = value;
}
附加2:
你也可以像这样定义DynamicObject
:
public class DynamicObject : Dictionary<Field, object>
{
public void Set<T>(GenericField<T> field, T value)
{
this[field] = value;
}
}
现在您可以设置如下值:
obj.Set(UsernameField.Instance, "Sue");
这是类型安全的,看起来更自然。 Set
中的GenericField
方法现已过时。
答案 1 :(得分:1)
我可能只是忽略了问题的重点,但如果目标是减少实现类型安全所需的代码重复量,那么为什么不根据字段类型而不是字段本身来定义访问者助手:< / p>
public abstract class StringField<T> : Field
{
public static void Set(DynamicObject obj, string value)
{
obj[typeof(T)] = someValue;
}
}
public class UsernameField : StringField<UsernameField> { }
// To set fields
UsernameField.Set(obj, someValue);
编辑:
或者你可以在没有Singleton的情况下使用Oliver's solution的轻微变化:
public abstract class GenericField<T, U> : Field
{
public static void Set(DynamicObject obj, T value)
{
obj[typeof(U)] = value;
}
}
public class UsernameField : GenericField<string, UsernameField> { }
答案 2 :(得分:1)
Fields
不应该知道DynamicObject
来获取和设置值。您可以让DyanmicObject
处理值的获取和设置,但我认为更好的方法是将DynamicObject
视为Field
个实例的集合,并且每个字段都有自己的值。像这样:
interface IField
{
object Value { get; set; }
}
interface IField<T> : IField
{
new T Value { get; set; }
}
abstract class BaseField<T> : IField<T>
{
T _value;
public T Value
{
get { return _value; }
set
{
// could add stuff like OnValueChanging, IsValueValid, etc...
this._value = value;
}
}
object IField.Value
{
get { return Value; }
set { Value = (T)value; }
}
}
class DynamicObject : List<IField>
{
public TField GetField<TField>() where TField : IField
{
return this.OfType<TField>().Single();
}
}
然后用法是:
class UsernameField : BaseField<string> { }
[TestMethod]
public void test()
{
var parent = new DynamicObject();
var field = new UsernameField() { Value = "the username" };
parent.Add(field);
Assert.AreEqual("the username", parent.GetField<UsernameField>().Value);
}