C
C#6mo ago
Legion

✅ Does anyone know why Validator.ValidateObject only validates properties and not fields?

And is there a way to make it work with fields?
31 Replies
Angius
Angius6mo ago
Why validate fields? They're an implementation detail
Mayor McCheese
Mayor McCheese6mo ago
So... because that's how it's written. Deep down in the bowels of the the validate object code you have ...
private static ICollection<KeyValuePair<ValidationContext, object>> GetPropertyValues(object instance, ValidationContext validationContext) {

PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(instance);
List<KeyValuePair<ValidationContext, object>> items = new List<KeyValuePair<ValidationContext, object>>(properties.Count);
foreach (PropertyDescriptor property in properties) {
ValidationContext context = CreateValidationContext(instance, validationContext);
context.MemberName = property.Name;

if (_store.GetPropertyValidationAttributes(context).Any()) {
items.Add(new KeyValuePair<ValidationContext, object>(context, property.GetValue(instance)));
}
}
return items;
}
private static ICollection<KeyValuePair<ValidationContext, object>> GetPropertyValues(object instance, ValidationContext validationContext) {

PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(instance);
List<KeyValuePair<ValidationContext, object>> items = new List<KeyValuePair<ValidationContext, object>>(properties.Count);
foreach (PropertyDescriptor property in properties) {
ValidationContext context = CreateValidationContext(instance, validationContext);
context.MemberName = property.Name;

if (_store.GetPropertyValidationAttributes(context).Any()) {
items.Add(new KeyValuePair<ValidationContext, object>(context, property.GetValue(instance)));
}
}
return items;
}
NB: I redacted some silverlight code from the above. So validate object won't ever know about fields. DataAnnotations leaves a lot to be desired. Looking at the source, you may be able to use Validateor.ValidateValue; but I"m not certain.
Angius
Angius6mo ago
The same way you don't test private methods, you don't validate private fields And if it's public it should be a property anyway And those can be validated
Mayor McCheese
Mayor McCheese6mo ago
I'm assuming he means public int SomeField; which yeah, I also fundamentally disagree with, use a property.
Legion
Legion6mo ago
I guess im off the mind set that one should use a field until they need it to be a property, which is why im a little confused by this limitation
Angius
Angius6mo ago
You're of the wrong mindset, then
Mayor McCheese
Mayor McCheese6mo ago
Annoyingly there is this, where validation attributes can be on fields.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
[SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "We want it to be accessible via method on parent.")]
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want users to be able to extend this class")]
public class RangeAttribute : ValidationAttribute {
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
[SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "We want it to be accessible via method on parent.")]
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want users to be able to extend this class")]
public class RangeAttribute : ValidationAttribute {
There's not a large cost to use a property; introducing a property later is a breaking change that is hard to fix.
Legion
Legion6mo ago
a little salty :/
Mayor McCheese
Mayor McCheese6mo ago
but not wrong
Legion
Legion6mo ago
i wasnt aware, is that because of reflection differences?
Angius
Angius6mo ago
Encapsulation, changing it is breaking
Mayor McCheese
Mayor McCheese6mo ago
yesn't, if you're deploying your whole app it's not a breaking change if you're not using reflection, if you are using reflection it may be a breaking change; if you're deploying using multiple assemblies and only a partial deployment then it's a breaking change.
Legion
Legion6mo ago
fair enough, i'll switch them over
Angius
Angius6mo ago
Also, for purely pragmatic purposes, every library will assume properties are public and fields are private Fields don't get serialized to JSON, for example Fields cannot be used in DI Etc.
Legion
Legion6mo ago
thats good to know, untill now i kinda felt that { get; set; } was essentially a more verbose way of declaring a field unless you implemented them
Mayor McCheese
Mayor McCheese6mo ago
properties create methods, like <GetName()> and <SetName(string value)>; and fields don't; so a partial deployment creates a breaking change when updated from a field to a property.
Angius
Angius6mo ago
$getsetdevolve
MODiX
MODiX6mo ago
class Foo
{
private int _bar;

public int GetBar()
{
return _bar;
}

public void SetBar(int bar)
{
_bar = bar;
}
}
class Foo
{
private int _bar;

public int GetBar()
{
return _bar;
}

public void SetBar(int bar)
{
_bar = bar;
}
}
can be shortened to
class Foo
{
private int _bar;

public int GetBar() => _bar;

public void SetBar(int bar) => _bar = bar;
}
class Foo
{
private int _bar;

public int GetBar() => _bar;

public void SetBar(int bar) => _bar = bar;
}
can be shortened to
class Foo
{
private int _bar;
public int Bar {
get { return _bar; }
set { _bar = value; }
}
}
class Foo
{
private int _bar;
public int Bar {
get { return _bar; }
set { _bar = value; }
}
}
can be shortened to
class Foo
{
private int _bar;
public int Bar {
get => _bar;
set => _bar = value;
}
}
class Foo
{
private int _bar;
public int Bar {
get => _bar;
set => _bar = value;
}
}
can be shortened to
class Foo
{
public int Bar { get; set; }
}
class Foo
{
public int Bar { get; set; }
}
Mayor McCheese
Mayor McCheese6mo ago
you can declare more explicit getters and setters, that become the guts of methods like <GetName()>, let's say if you wanted to return a default value or something.
Legion
Legion6mo ago
Kinda strange thing, type.GetProperties() does not return autoproperties, but GetFields() does unless theres something really buggy going on lol
MODiX
MODiX6mo ago
Angius
REPL Result: Success
using System.Reflection;

class Foo
{ public int A { get; set; } }

typeof(Foo).GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(p => p.Name)
using System.Reflection;

class Foo
{ public int A { get; set; } }

typeof(Foo).GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(p => p.Name)
Result: List<string>
[
"A"
]
[
"A"
]
Compile: 463.180ms | Execution: 59.138ms | React with ❌ to remove this embed.
Angius
Angius6mo ago
For reasons unknown to me, you need to specify at least two flags: * Static or Instance * Public or NonPublic Otherwise, you get an empty array
Legion
Legion6mo ago
i think its just a problem with my source generator
Angius
Angius6mo ago
You're using reflections in a source generator?
Legion
Legion6mo ago
no, im using a source generator to autofill some XML annotations, but the old generated file with the member is still a field apparently
Mayor McCheese
Mayor McCheese6mo ago
I feel like .... that didn't use to be the case?
Angius
Angius6mo ago
I can see references to it on SO going as far back as 2013 Probably even earlier
Mayor McCheese
Mayor McCheese6mo ago
I might be conflating with something else with reflection, do you need to do that for attributes?
Legion
Legion6mo ago
i fight a lot with the XmlSerializer, but anyway its a different issue :P how do i mark this as solved?
Pobiega
Pobiega6mo ago
/close
Legion
Legion6mo ago
thanks,