重构此代码的优雅方法是什么?
说,我有以下对象
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;
有更好的方法吗?
谢谢,
志
答案 0 :(得分:6)
当我真的不关心特定代码行会发生什么时,我会这样做:
ExecuteIgnoringExceptions(() => textBox.Text = Some.Possibly.Bad.Value);
void ExecuteIgnoringExceptions(Action a)
{
try
{
a.Invoke();
}
catch
{
}
}
答案 1 :(得分:2)
我首先将Int
检查从get转移到set属性。如果它不应该小于0,那么不要让它设置为小于0.对于日期,在setter中使用TryParse
方法使其异常安全。然后确保在BirthDay属性中使用私有的setter。
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;
}
}
}
}
重点是价值应该在途中而不是在出路上进行验证。
答案 2 :(得分:1)
尝试这些扩展方法;他们将执行空值检查(通过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);
答案 3 :(得分:1)
如果您不需要存储无效的生日字符串,那么我会将BirthDay
设为正常DateTime?
或DateTime
属性。然后解析输入值并将解析结果分配给属性。
如果你真的需要存储字符串,可以将生日情况重写为:
public DateTime? BirthDay
{
get
{
DateTime result;
if(DateTime.TryParse(BirthDayString,out result))
return result;
else
return null;
}
}
答案 4 :(得分:0)
这是一个选项 - 不要在属性赋值中抛出基本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;
}
}
答案 5 :(得分:0)
我会尽力避免从属性getter中抛出异常。框架设计指南说:
避免抛出异常 财产瘾君子。物业吸气剂 应该是简单的操作和应该 没有先决条件。如果是一个吸气剂 它应该抛出异常 可能会被重新设计成一种方法。 请注意,此规则不适用于 索引器,我们期待的地方 验证的例外情况 争论。请注意这一点 准则仅适用于财产 干将。抛出一个是可以的 属性设置器中的异常。
开发人员已经习惯于几乎在所有时间都能安全地呼叫财产。他们不希望getter执行复杂的计算或需要大量的CPU时间来完成。他们当然希望他们不会抛出异常。在大多数情况下,BCL和其他所有人都遵循本指南,建议您也这样做。