问题描述:

I need to associate a program options/parameters to ViewModel class properties ( which are binded to view's controls). So i thought to create a class.

internal class OptionValue

{

OptionSource Source { get; set; }

Type OptionType { get; set; }

string OptionName { get; set; }

// ?? Property { get; set;} ??

object DefaultValue { get; set; }

}

Then I would create an IEnumerable in OptionsViewModel class and do things with it.

public class OptionsViewModel : ViewModel

{

OptionValue[] Options =

{

new OptionValue(OptionSource.SysParameter, typeof(Int32), "SelectorSingleSelection", SingleSelection, 0),

new OptionValue(OptionSource.ConfigFileProperty, typeof(Double), "SelectorTolerance", Tolerance, 0.002),

...

}

public int SingleSelection { get; set; }

public double Tolerance { get; set; }

void Init()

{

foreach (var optVal in Options)

{

FetchValueFromOptionSourceAndSetToProperty(optVal);

}

}

void Save()

{

foreach (var optVal in Options)

{

StoreValueFromPropertyToOptionSource(optVal);

}

}

I'm completely lost how to do this. OptionSource is my enum and has no role for this question.

网友答案:

It is not entirely clear what you are asking here, hence the close votes. Still, I think commenter Jon Skeet's guess that you seem to be looking for a way to access properties dynamically by name is likely to be correct. So with that in mind...

The most obvious, straightforward way to do what you seem to be asking is in fact to use the PropertyInfo type. For example…

abstract class OptionValue<TTarget>
{
    private PropertyInfo _propertyInfo;

    public TTarget TargetObject { get; private set; }
    public OptionSource Source { get; private set; }
    public string OptionName { get { return _propertyInfo.Name; } }

    protected OptionValue(TTarget targetObject, OptionSource source, string optionName)
    {
        TargetObject = targetObject;
        Source = source;

        _propertyInfo = typeof(TTarget).GetProperty(optionName);
    }

    public object GetValue()
    {
        return _propertyInfo.GetValue(TargetObject);
    }

    public void SetValue(object value)
    {
        _propertyInfo.SetValue(TargetObject, value);
    }

    public abstract void SetValueFromText(string text);

    public static OptionValue<TTarget, TProperty> Create<TTarget, TProperty>(
        TTarget targetObject, TProperty defaultValue, OptionSource source, string optionName)
    {
        return new OptionValue<TTarget, TProperty>(targetObject, defaultValue, source, optionName);
    }
}

class OptionValue<TTarget, TProperty> : OptionValue<TTarget>
{
    public TProperty DefaultValue { get; private set; }

    public OptionValue(TTarget targetObject, TProperty defaultValue,
        OptionSource source, string optionName)
        : base(targetObject, source, optionName)
    {
        DefaultValue = defaultValue;
    }

    public TProperty GetValue()
    {
        return (TProperty)base.GetValue();
    }

    public void SetValue(TProperty value)
    {
        base.SetValue(value);
    }

    public override void SetValueFromText(string text)
    {
        SetValue((TProperty)Convert.ChangeType(text, typeof(TProperty)));
    }
}

I refactored that a bit, adding generic syntax to the types, in case that would make your use scenario easier. The abstract base class has almost everything you need; using generics does make it a bit easier to implement the SetValueFromText(), but that could be moved into the base class by using the type as declared in the property.

You might use the above like this:

class OptionsViewModel
{
    OptionValue<OptionsViewModel>[] Options;

    public OptionsViewModel()
    {
        Options = new OptionValue<OptionsViewModel>[]
        {
            OptionValue<OptionsViewModel>.Create(this, 0, OptionSource.SysParameter, "SingleSelection"),
            OptionValue<OptionsViewModel>.Create(this, 0.002, OptionSource.ConfigFileProperty, "Tolerance"),
        };
    }

    public int SingleSelection { get; set; }
    public double Tolerance { get; set; }

    public void Init()
    {
        foreach (OptionValue<OptionsViewModel> option in Options)
        {
            FetchValueFromOptionSourceAndSetToProperty(option);
        }
    }

    // Signature and implementation modified a bit just for illustration purposes.
    public string Save()
    {
        StringBuilder sb = new StringBuilder();

        foreach (OptionValue<OptionsViewModel> option in Options)
        {
            sb.AppendLine(StoreValueFromPropertyToOptionSource(option));
        }

        return sb.ToString();
    }

    private void FetchValueFromOptionSourceAndSetToProperty(OptionValue<OptionsViewModel> option)
    {
        // Obviously, some actual code that would retrieve values would
        // go here for a real program.
        switch (option.OptionName + "Selector")
        {
            case "SingleSelectionSelector":
                option.SetValue(17);
                break;
            case "ToleranceSelector":
                option.SetValueFromText("3.141592");
                break;
        }
    }

    private string StoreValueFromPropertyToOptionSource(OptionValue<OptionsViewModel> option)
    {
        // Likewise, in your actual code you wouldn't even return
        // anything here, and the code would actually store the value
        // to some appropriate location.
        return option.GetValue().ToString();
    }
}

Of course in the above I've modified your original example a bit just so that the code can be easily run in a simple test program, rather than requiring whatever larger environment you're actually working in.

Finally, a simple test program to illustrate the above working:

class Program
{
    static void Main(string[] args)
    {
        OptionsViewModel model = new OptionsViewModel();

        model.Init();
        Console.WriteLine(model.Save());
    }
}

Note that using the PropertyInfo class this way does incur a bit of a performance hit. Assuming these operations are done infrequently, and/or in the context of I/O, this is probably not an issue.

But note that there are other ways to accomplish the same thing. These alternatives include:

  1. Getting the PropertyInfo but then using it with the Expression class to compile a delegate instance to set the property value.
  2. Simply having the caller provide the necessary delegate instance by writing it explicitly as an anonymous method.
  3. Completely changing the whole architecture and using one of the serialization methods built into .NET.

I hope the above gets you on the right track. The original code example in your question is fairly vague and leaves out the very important detail of how you would expect to use this Options class. I assume that given the above nudge in the right direction, you can adapt the concept to your needs.

相关阅读:
Top