假设我们使用这个类:
public class UsefulClass
{
public string A { get; set; }
public string B { get; set; }
public int? C { get; set; }
public int? D { get; set; }
public decimal E { get; set; }
public decimal F { get; set; }
}
让我们考虑以下实例:
UsefulClass z_objUsefulInstance = new UsefulClass()
{
A = null,
C = null,
E = 0
};
此时,z_objUsefulInstance.A
和C
null
,E
为0,B
,D
和F
尚未初始化。
有没有办法自动告诉z_objUsefulInstance
的哪些属性尚未初始化,哪些属性已使用null
或 0 初始化?
编辑:通过大众需求,为什么我需要这个:模拟类似于EntityFramework的数据库访问系统。现在所有属性都是特定的泛型类型,因此很容易知道哪个是null
的,哪个是Generic<T>.HasNullValue == true
的。但是这种泛型类型会导致各种问题,现在我们想摆脱它,特别是当我们越来越熟悉Expression
时。
没有办法自动告诉z_objUsefulInstance的哪些属性尚未初始化,哪些属性已初始化为null或0?
除非拦截属性 setter并设置某种标志,否则您无法真正知道在运行时轻松检查已设置的属性。 从第一主体的角度来看,类似于这样:
public class UsefulClass
{
public string A { get => _a; set { _a = value; A_Set = true; } }
private string _a;
private bool A_Set = false;
public string B { get => _b; set { _b = value; B_Set = true; } }
private string _b;
private bool B_Set = false;
public int? C { get => _c; set { _c = value; C_Set = true; } }
private string _c;
private bool C_Set = false;
public int? D { get => _d; set { _d = value; D_Set = true; } }
private string _d;
private bool D_Set = false;
public decimal E { get => _e; set { _e = value; E_Set = true; } }
private string _e;
private bool E_Set = false;
public decimal F { get => _f; set { _f = value; F_Set = true; } }
private string _f;
private bool F_Set = false;
}
它非常冗长,但您可以在这里看到我们根本没有比较值,我们可以明确确定是否设置了每个属性,在实例初始化期间没有特别考虑,这个简单的代码仅跟踪每个属性是否被设置。
因此,在您的初始化之后,我们可以检查这些新标志:
UsefulClass z_objUsefulInstance = new UsefulClass()
{
A = null,
C = null,
E = 0
};
Console.WriteLine(z.C_Set); // True
Console.WriteLine(z.D_Set); // False
我们可以使用后备存储和帮助程序方法的字典来简化此操作,以get
和set
属性值,我们甚至可以将该逻辑封装在基类中,使其更易于使用:
public class UsefulClass : PropertyTracker
{
public string A { get => GetProperty<string>(); set => SetProperty(value); }
public string B { get => GetProperty<string>(); set => SetProperty(value); }
public int? C { get => GetProperty<int?>(); set => SetProperty(value); }
public int? D { get => GetProperty<int?>(); set => SetProperty(value); }
public decimal E { get => GetProperty<decimal>(); set => SetProperty(value); }
public decimal F { get => GetProperty<decimal>(); set => SetProperty(value); }
}
public abstract class PropertyTracker
{
private Dictionary<string, object> _values = new Dictionary<string, object>();
protected void SetProperty<T>(T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
{
_values[propertyName] = value;
}
protected T GetProperty<T>([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
{
if (!_values.ContainsKey(propertyName))
return default;
return (T)_values[propertyName];
}
public bool IsSet(string propertyName)
{
return _values.ContainsKey(propertyName);
}
}
看到我们仍然有后备商店的概念,它不再是一个领域了。检查代码也有点不同:
UsefulClass z_objUsefulInstance = new UsefulClass()
{
A = null,
C = null,
E = 0
};
Console.WriteLine(z.IsSet(nameof(UsefulClass.C)); // True
Console.WriteLine(z.IsSet(nameof(UsefulClass.D)); // False
您可以使用各种技术在类中搭建此代码或类似代码的基架,这只是一个示例实现。您甚至可以编写一个使用反射来执行相同操作的通用包装器。在我的解决方案中,我倾向于使用 T4 模板来生成有效的视图模型类。我的主要论点是,我可以生成一些冗长的代码并在编译时受到打击,而不是在运行时使用基于反射的实现来影响性能。
如果 ViewModel 类继承自模型类,则可以接近于与运行时的其余部分更兼容的明显自动实现,但这需要将属性声明为virtual
以使继承类能够重写实现。
- 如果你最终走上了这条路,请考虑通过实现
INotifyPropertyChanged
来增加你的类的价值,或者当你在那里时IChangeTracking
或IRevertibleChangeTracking
。
UsefulClass z_objUsefulInstance = new UsefulClass() { A = null C = null, E = 0 };
此时,
z_objUsefulInstance.A
和C
为 null,E
为 0,B
、D
和F
尚未初始化。
不,这不太对。
来自 C#7 语言规范中的"14.11.4 构造函数执行">
变量初始值设定项将转换为赋值语句,这些赋值语句在调用基类实例构造函数之前执行。
因此,在开始执行上述示例中的实例构造函数之前,将分配属性
A = default(string); // null
B = default(string); // null
C = default(int?); // null
D = default(int?); // null
E = default(decimal); // 0.0m
F = default(decimal); // 0.0m
(不太准确,但足够接近这个答案)
然后运行实例构造函数(在此示例中为编译器提供的默认值),然后进行属性赋值
A = null,
C = null,
E = 0
.E = 0
和E = default(decimal)
之间没有区别,null
和null
之间也没有区别(默认值(字符串))。
如果需要判断是否设置了属性,则必须提供支持字段,或以其他方式控制对该属性的访问。
如果您想了解有关构造函数详细信息的更多信息,可以在 https://jonskeet.uk/csharp/constructors.html 中找到比语言规范更友好的摘要。