简体   繁体   中英

What's the accepted way to handle values that can be different data types in C#?

I'm basically reading a config file

[Section] Key=value

Where the value can be either a string, an integer, a double, or a boolean value.

While working in this context, I have a class that looks like this...

public class Setting
{
    public string Section {get; set;}
    public string Key {get; set;}
    public <string, int, double, or bool> Value {get; set;}

    public Setting(string section, string key, <string, int, double, or bool> value)
    {
        Section = section;
        Key = key;
        Value = value;
    }

    public void Write()
    {
        //if Value is an int, call third-party code to write an integer to the config file.
        //if Value is a string, call third-party code to write a string to the config file.
        //...
    }
}

In this situation, what is the accepted way to handle the Value property of this class?

In addition, I'd like to be able to store a bunch of these objects in an Array, List, or other types of collections.


UPDATE:
I'm not reading/writing to the configuration file directly, that part of the code is not controlled by me. Basically, I need to call different functions in the third-party code, based on the type of Value


UPDATE:
One thought was to use generics, and have a class like this...

public class Setting<T>
{
    public string Section { get; set; }
    public string Key { get; set; }
    public T Value { get; set; }

    public Setting(string section, string key, T value)
    {
        Section = section;
        Key = key;
        Value = value;
    }

    public void Write()
    {
        switch (Type.GetTypeCode(typeof(T)))
        {
            case TypeCode.Int32:
                //Call third-party code to write an integer
                break;
            case TypeCode.String:
                //Call third-party code to write a string
                break;
            default:
                break;
        }
    }
}

But then I'd only be able to store a single type of setting in a List.

System.Collections.Generic.List<Setting<string>> settings = new List<Setting<string>>();

So I'd have to have a list for each type of setting.


UPDATE:
Another option might be to use and interface, and classes for each type of setting that implement the interface...

interface ISetting
{
    string Section { get; set; }
    string Key { get; set; }

    void Write();
}

public class StringSetting : ISetting
{
    public string Section { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }

    public StringSetting(string section, string key, string value)
    {
        Section = section;
        Key = key;
        Value = value;
    }

    public void Write()
    {
        //Call third-party code to write the setting.
    }
}

But that seems like a lot of duplicate code, so making changes in the future might be error prone.


UPDATE:
Another option, is to make Value a dynamic type.

public class DynamicSetting
{
    public string Section { get; set; }
    public string Key { get; set; }
    public dynamic Value { get; set; }

    public DynamicSetting(string section, string key, dynamic value)
    {
        Section = section;
        Key = key;
        Value = value;
    }

    public void Write()
    {
        switch (Type.GetTypeCode(Value.GetType()))
        {
            case TypeCode.Int32:
                //Call third-party code to write an integer
                break;
            case TypeCode.String:
                //Call third-party code to write a string
                break;
            default:
                break;
        }
    }
}

Then I can create a bunch of DynamicSetting objects, and store them in a collection like I want.

DynamicSetting IntSetting = new DynamicSetting("Section", "Key", 1);
DynamicSetting StringSetting = new DynamicSetting("Section", "Key", "1");
DynamicSetting DoubleSetting = new DynamicSetting("Section", "Key", 1.0);

System.Collections.Generic.List<DynamicSetting> settings = new List<DynamicSetting>();

settings.Add(IntSetting);
settings.Add(StringSetting);
settings.Add(DoubleSetting);

foreach(DynamicSetting setting in settings)
{
    setting.Write();
}

UPDATE:
I could also make Value an object

public class ObjectSetting
{
    public string Section { get; set; }
    public string Key { get; set; }
    public object Value { get; set; }

    public ObjectSetting(string section, string key, object value)
    {
        Section = section;
        Key = key;
        Value = value;
    }

    public void Write()
    {
        switch (Type.GetTypeCode(Value.GetType()))
        {
            case TypeCode.Int32:
                //Call third-party code to write an integer
                break;
            case TypeCode.String:
                //Call third-party code to write a string
                break;
            case TypeCode.Double:
                //Call third-party code to write a string
                break;
            default:
                break;
        }
    }
}

And it would work just like dynamic

ObjectSetting IntSetting = new ObjectSetting("Section", "Key", 1);
ObjectSetting StringSetting = new ObjectSetting("Section", "Key", "1");
ObjectSetting DoubleSetting = new ObjectSetting("Section", "Key", 1.0);

System.Collections.Generic.List<ObjectSetting> settings = new List<ObjectSetting>();

settings.Add(IntSetting);
settings.Add(StringSetting);
settings.Add(DoubleSetting);

foreach(ObjectSetting setting in settings)
{
    setting.Write();
}

The simplest way is to accept Value as an object in the constructor and the setter, both of which would validate the Type against your list of valid types. Use a Switch in your Write method to determine which third-party code to call. You can store all your Settings in a single collection. Alternatively, you could write overloads for the constructor and a SetValue method. That's a little more code, but would provide design time type-checking.

Example for ISettingValue:

public interface ISettingValue
{
    void Write();
}

public class StringSetting : ISettingValue
{
    readonly string _data;
    public StringSetting(string data) => _data = data;
    public void Write() 
    {
        //Call third-party code to write the string (value of _data).
    }
}
public class IntSetting : ISettingValue
{
    readonly int _data;
    public IntSetting(int data) => _data = data;
    public void Write() 
    {
        //Call third-party code to write the integer (value of _data).
    }
}

public class Setting
{
    public string Section { get; set; }
    public string Key { get; set; }
    public ISettingValue Value { get; set; }

    public Setting(string section, string key, ISettingValue value)
    {
        Section = section;
        Key = key;
        Value = value;
    }

    public void Write()
    {
        Value.Write();
    }
}

Maybe something like that?

    public abstract class Setting {
    public abstract Type keyType { get; }
    public string Key { get; protected set; }
    public object value { get; protected set; }

    protected abstract Action writer { get; }

    public void Write() => writer();

}
public class Setting<T> : Setting {
    public override Type keyType => typeof(T);
    protected override Action writer => () => typeWriter(Value);

    public string Section { get; set; }
    public T Value {get; set;}

    private Action<T> typeWriter { get; }


    public Setting(string section, string key, T value, Action<T> writer) {
        Section = section;
        Key = key;
        this.value = Value = value;
        typeWriter = writer;
    }
}

public class Usage {
    private List<Setting> settings = new List<Setting>() {
        new Setting<double>("", "x", 10, n => Debug.WriteLine(n % 4)),
        new Setting<string>("", "y", "abc", s => Debug.WriteLine(s.ToUpper())),
        new Setting<bool>("", "z", true, b => Debug.Write(!b)),
    };

    public Usage() {
        foreach (var s in settings) {
            Debug.Write($"{s.keyType.Name} {s.Key} =");
            s.Write();
        }

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM