Как правильно объявить ограничение

#c#

#c#

Вопрос:

У меня есть класс RuleCondition<TValue> :

 public class RuleCondition<TValue>
{
    public string PropertyToCheck { get; private set; }

    public TValue Value { get; private set; }

    public RuleCondition(string propertyToCheck, TValue value)
    {
        PropertyToCheck = propertyToCheck;
        Value = value;
    }
}
  

В Rule<T> классе у меня есть поле, в котором содержится его экземпляр:

 public abstract class Rule<T>
{
    private readonly string mPropertyName;
    private readonly object mError;
    private readonly RuleCondition<TValue> mCondition;

    protected Rule(string propertyName, object error, RuleCondition<TValue> condition)
    {
        mCondition = condition;
    }

    // ...
}
  

Когда я теперь хочу добавить my RuleCondition<T> в a Rule (который наследуется от Collection<Rule<T>> и DelegateRule<T> наследуется от Rule<T> ) следующим образом

 Rules.Add(new DelegateRule<ConnectionSettingsViewModel>(nameof(Username),
          "Username: Cannot be empty, starting with a space or a backslash.",
          x => !string.IsNullOrWhiteSpace(x.Username),
          new RuleCondition<LoginOptions>(nameof(LoginUsage), LoginOptions.Instrument)));
  

Я получаю сообщение об ошибке, что тип не соответствует, что правильно. Теперь моя проблема: как получить значение TValue для RuleCondition исключения необходимости объявлять его «сверху» и разбивать весь мой код. Как-то неправильно объявлять ограничение везде только для того, чтобы получить его в RuleCondition<TValue> классе.

Есть ли более простой способ? Что я упускаю?

РЕДАКТИРОВАТЬ: T уже имеет тип *ViewModel .

Я думаю, мне нужно добавить больше объяснений:

Здесь начинается «цепочка»:

 public class ConnectionSettingsViewModel : NotifyDataErrorInfo<ConnectionSettingsViewModel>
{ }
  

Если бы я теперь добавил TValue ограничение к Rule классу таким образом Rule<T, TValue> , мне также нужно предоставить это ограничение NotifyDataErrorInfo классу. Итак, к «верхнему» классу. Но это кажется неправильным. Чтобы это ограничение было объявлено в каждом классе между the NotifyDataErrorInfo и «последним» классом RuleCondition .

РЕДАКТИРОВАТЬ 2: я взял код отсюда, чтобы реализовать проверку элемента управления в моем приложении MVVM. Но я подумал, что мне нужно выполнить некоторые условия для нескольких элементов управления. Так что правило не будет «выполняться» каждый раз, а только в том случае, если проверено определенное RadioButton , например.

Комментарии:

1. Я думаю private readonly RuleCondition<TValue> mCondition; , что должно быть private readonly RuleCondition<T> mCondition; так, чтобы он использовал T то, с чем был объявлен класс.

2. Не может быть, потому T что уже имеет тип *ViewModel . Мне нужно другое ограничение, чтобы получить TValue . Но если я добавлю это, как Rule<T, TValue> я получаю ошибки, которые в нескольких местах я должен указать этот тип. Что имеет смысл. Но кажется неправильным объявлять его во многих местах, просто чтобы получить его в RuleCondition<TValue> классе.

3. Нужно ли вам знать Type Value свойство при обработке условий? Можете ли вы работать с object вместо этого?

4. @DarjanBogdan Я думал об этом. Но тогда я должен использовать некоторое отражение, чтобы получить тип. Мне нужно иметь по крайней TValue мере, быть enum и быть bool .

5. Какова связь между NotifyDataErrorInfo<ConnectionSettingsViewModel> и Rule<T> , наследует ли он его?

Ответ №1:

Основываясь на ваших данных и этом сообщении в блоге, вы можете сделать что-то вроде этого. Примите во внимание, что вам нужно будет немного адаптировать классы, представленные в этом блоге.

Во-первых, вы можете создать ConditionalRule<T, TValue> то, которое наследует DelegateRule<T> (или любое другое Rule<T> , которое вы хотите). Для этого вам нужно будет распечатать DelegateRule class

 public class ConditionalRule<T, TValue> : DelegateRule<T>
{
    public TValue Value { get; private set; }

    public ConditionalRule(string propertyName, object error, Func<T, bool> rule, TValue value)
        : base(propertyName, error, rule)
    {
        Value = value;
    }

    public override bool Apply(T obj)
    {
        // do your business driven condition check before calling
        return base.Apply(obj);
    }
}
  

Во-вторых, вы должны расширить RuleCollection<T> с помощью дополнительного универсального метода:

 public sealed class RuleCollection<T> : Collection<Rule<T>>
{
    #region Public Methods

    /// <summary>
    /// Adds a new <see cref="Rule{T}"/> to this instance.
    /// </summary>
    /// <param name="propertyName">The name of the property the rules applies to.</param>
    /// <param name="error">The error if the object does not satisfy the rule.</param>
    /// <param name="rule">The rule to execute.</param>
    public void Add(string propertyName, object error, Func<T, bool> rule)
    {
        this.Add(new DelegateRule<T>(propertyName, error, rule));
    }

    public void AddConditional<TValue>(string propertyName, object error, Func<T, bool> rule, TValue value)
    {
        this.Add(new ConditionalRule<T, TValue>(propertyName, error, rule, value)
    }

    //....
    #endregion
}
  

Кроме того, вам нужно будет адаптировать свой код, который добавляет правила в коллекцию, в зависимости от правил, которые вы хотите добавить:

 Rules.AddConditional<LoginOptions>(
    new ConditionalRule<ConnectionSettingsViewModel, LoginOptions>(
        nameof(Username),
        "Username: Cannot be empty, starting with a space or a backslash.",
        x => !string.IsNullOrWhiteSpace(x.Username),
        LoginOptions.Instrument
    )
);
  

Пожалуйста, обратите внимание, я не проверял код, поэтому вы можете ожидать некоторых проблем во время компиляции, но общая идея заключалась бы в создании определенных правил Rule<T> использования наследования. Таким образом, каждое правило будет хорошо вписываться в RuleCollection<T>

Комментарии:

1. Спасибо, Дарьян, проверю это!

2. Бодган, мне пришлось немного его подправить. Но нет, я заставил это работать. Правило будет применено только в том случае, если выполнено условие. Еще раз спасибо!

3. @FredM Рад это слышать! 🙂