Какой элегантный способ попробовать поймать свойства в C # - PullRequest
4 голосов
/ 08 июня 2011

Какой элегантный способ рефакторинга этого кода?

Скажем, у меня есть следующий объект

public class SomeObject
{
    public SomeInnerObject1 Cat { get; set; }
    public SomeInnerObject2 Dog { get; set; }

    public class SomeInnerObject1
    {
        public int Age { get; set; }

        public string AgeAsString
        {
            get
            {
                if(Age < 0 )
                    throw new Exception();

                return Age.ToString();
            }
        }
    }

    public class SomeInnerObject2
    {
        public string BirthDayString { get; set; }
        public DateTime BirthDay { get { return DateTime.Parse(BirthDayString); } }
    }
}

И, скажем, мне нужно установить несколько значений текстовых полей, которые мне нужно установить

var obj = new SomeObject
          {
              Cat = new SomeObject.SomeInnerObject1 {Age = -1},
              Dog = null
          };

//These will pass but it looks ugly
try
{
    textbox1.Text = obj.Dog.BirthDay.Month.ToString();
}
catch{ }

try
{
    textbox2.Text = obj.Cat.AgeAsString;
}
catch { }

//These will fail
textbox1.Text = obj.Dog.BirthDay.Month.ToString();
textbox2.Text = obj.Cat.AgeAsString;

Есть ли лучший способ сделать это?

Спасибо,

Чи

Ответы [ 6 ]

6 голосов
/ 08 июня 2011

Когда мне действительно все равно, что происходит с определенной строкой кода, я делаю что-то вроде этого:

ExecuteIgnoringExceptions(() => textBox.Text = Some.Possibly.Bad.Value);

void ExecuteIgnoringExceptions(Action a) 
{
    try
    {
        a.Invoke();
    }
    catch
    {
    }
}
2 голосов
/ 08 июня 2011

Я бы начал с перемещения чека Int из свойства get в set.Если он не должен быть меньше 0, то не позволяйте ему быть установленным меньше 0. Для даты используйте метод TryParse в установщике, чтобы сделать исключение безопасным.Затем убедитесь, что используете частный установщик в свойстве BirthDay.

public class SomeObject
{
    public SomeInnerObject1 Cat { get; set; }
    public SomeInnerObject2 Dog { get; set; }

    public class SomeInnerObject1
    {
        private int _Int = 0;
        public int Int {
            get
            {
                return _Int;
            }
            set
            {
                if(value < 0) 
                    throw new Exception("Int must be greater than or equal to 0.");
                else
                    _Int = value;
            }
       }

        public string String
        {
            get
            {
                return Int.ToString();
            }
        }
    }

    public class SomeInnerObject2
    {
        private string _BirthDayString = "";
        public string BirthDayString
        {
            get
            {
                return _BirthDayString;
            }
            set
            {
                DateTime newDate;
                if(DateTime.TryParse(value, newDate))
                    BirthDay = newDate;
                else
                    throw new ArgumentException("Birthday string must be a properly formatted date.");
            }
        }

        private DateTime _BirthDay = DateTime.MinValue;
        public DateTime BirthDay
        {
            get 
            {
                return _BirthDay;
            }
            private set
            {
                _BirthDay = value;
            }
        }
    }
}

Суть в том, что значения должны проверяться при входе, а не при выходе.

1 голос
/ 08 июня 2011

Если вам не нужно хранить недопустимые строки дня рождения, тогда я бы сделал BirthDay обычным свойством DateTime? или DateTime.Затем выполните синтаксический анализ входного значения и присвойте результат анализа свойству.

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

public DateTime? BirthDay
{
  get
  {
    DateTime result;
    if(DateTime.TryParse(BirthDayString,out result))
      return result;
    else
      return null;
   }
}
1 голос
/ 08 июня 2011

Попробуйте эти методы расширения;они выполнят нулевую проверку (через try-catch) и вернут значение по умолчанию (которое можно указать, если вы не хотите использовать .NET по умолчанию):

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the specified default value.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <param name="defaultValue">The default value to use if the projection or any parent is null.</param>
    /// <returns>the result of the lambda, or the specified default value if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection, TOut defaultValue)
        where TOut : class
    {
        try
        {
            return projection(input) ?? defaultValue;
        }
        //Catches attempts to access a child of a null object
        catch (NullReferenceException)
        {
            return defaultValue;
        }
        //Catches attempts to access the value of a null Nullable<T>
        catch (InvalidOperationException)
        {
            return defaultValue;
        }
    }

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the default value for the type.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <returns>the result of the lambda, or default(TOut) if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection)
        where TOut : class
    {
        return input.ValueOrDefault(projection, default(TOut));
    }

Использование:

//no try-catch needed
textbox1.Text = obj.ValueOrDefault(o=>o.Dog.BirthDay.Month.ToString(), String.Empty);
0 голосов
/ 08 июня 2011

Я бы очень старался избегать исключений от получателей свойства. В Руководстве по проектированию рамок сказано:

ИЗБЕГАЙТЕ бросать исключения из добытчики имущества. Собственники должны быть простыми операциями и должны не имеют предпосылок. Если добытчик может бросить исключение, оно должно вероятно, будет переработан, чтобы быть методом. Обратите внимание, что это правило не распространяется на индексаторы, где мы ожидаем исключения в результате проверки аргументы. Обратите внимание, что это Руководство относится только к собственности добытчики. Это нормально, чтобы бросить исключение в установщике свойств.

Застройщики привыкли к тому, что они могут звонить владельцам недвижимости почти всегда. Им не нужен метод получения, который выполняет сложные вычисления или требует много процессорного времени для выполнения. И они, конечно, предпочли бы, чтобы они не бросали исключения. По большей части BCL и все остальные следуют этому руководству, и желательно, чтобы вы тоже.

0 голосов
/ 08 июня 2011

Вот один вариант - не выбрасывать базовый класс Exception в присваиваниях свойств, а затем оборачивать присваивания в метод, который может предоставить значение по умолчанию.

Что-то быстрое и грязное, например:

//assignment will throw
var x = SafeAssign(()=>((string)(null)).ToString(),"test");

T SafeAssign<T>(Func<T> input, T defaultValue)
{
    try
    {
        return input();
    }
    catch //todo - catch better exceptions
    {
        return defaultValue;
    }
}
...