Type-safe configuration of objects with a dictionary that has a key "Type"

I have a general dictionary of objects where the key is of type Type :

 public class DynamicObject : IDictionary<Type, object> 

The idea is that this object is shared in architecture with plugins, so the type (which may be in the .dll plugin) is used as a key to prevent collisions between plugins. This type is also used to store metadata about the field (for example, a description of this field or the actual type of this field).

Currently, plugins need to set the field value for this object using code similar to this:

 DynamicObject someObject; string someValue; someObject[typeof(UsernameField)] = someValue; 

The only problem is that it is not a safe type, even if the UsernameField type knows the exact type of the expected value (for example, int or in this case string ), the value given here is simply entered as an object. I would like to use generics to make setting / getting a property type safe, but I'm not sure how to do it. So far, the best I've come up with is:

 // 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); 

This is a safe type, however it means that each of my field types (e.g. UsernameField ) has an almost identical static Set method.

How can I securely access values ​​in this way without using almost the same methods for each of my field types?

As an aside, using Type as a key like this is a good idea or are there hidden traps that I don't know about yet?

+6
source share
3 answers

Define the interface that all your plugins should implement:

 public interface IPlugin { string Name { get; } string Author { get; } string Description { get; } void Init(); } 

And then use Dictionary<Type, IPlugIn> .

Typically, an interface is declared in a separate dll (for example, "MyCompany.MyProject.PlugIns.Contracts.dll").


EDIT: Well, I think I know what you mean now.

The trick is to have a common class between Field and UsernameField using the common Set method. The fact that Field not generic makes all field types assignable to it. This would not be the case if it were declared as 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 } 

Since we need to call GetType in the Set method, we cannot declare it as static . Therefore, I used the singleton pattern.

Now we can set the field in a safe way:

 UsernameField.Instance.Set(obj, "Joe"); 

APPENDIX 1:

Since fields are now single, you can use fields as a dictionary key instead of your type.

 public class DynamicObject : IDictionary<Field, object> { } 

And Set will become:

 public void Set(DynamicObject obj, T value) { obj[this] = value; } 

APPENDIX 2:

You can also define DynamicObject as follows:

 public class DynamicObject : Dictionary<Field, object> { public void Set<T>(GenericField<T> field, T value) { this[field] = value; } } 

Now you can set these values:

 obj.Set(UsernameField.Instance, "Sue"); 

It is a safer type and seems more natural. The Set method in GenericField deprecated.

+2
source

I just don’t have the point of the question, but if the goal is to reduce the amount of code duplication necessary to ensure type safety, then why not define accessory helpers based on the type of the field, and not the field itself: / 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); 

Edit:

Or you can use a small change to the Oliver solution without Singleton:

 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> { } 
+1
source

Fields do not need to know about DynamicObject to get and set a value. You can get DyanmicObject handle getting and setting values, but I think the best approach is to treat DynamicObject as a collection of Field instances, and each field has its own meaning. Something like that:

 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(); } } 

And the use will then be:

 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); } 
+1
source

Source: https://habr.com/ru/post/905520/


All Articles