问题
Say you have a class declaration, e.g.:
class MyClass
{
int myInt=7;
int myOtherInt;
}
Now, is there a way in generic code, using reflection (or any other means, for that matter), that I can deduce that myInt has a default value assigned, whereas myOtherInt does not? Note the difference between being initialised with an explicit default value, and being left to it's implicit default value (myOtherInt will be initialised to 0, by default).
From my own research it looks like there is no way to do this - but I thought I'd ask here before giving up.
[Edit]
Even with nullable and reference types I want to distingush between those that have been left as null, and those that have been explicitly initialised to null. This is so that I can say that fields with an initialiser are "optional" and other fields are "mandatory". At the moment I'm having to do this using attributes - which niggles me with their redundancy of information in this case.
回答1:
I compiled your code and load it up in ILDASM and got this
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.7
IL_0002: stfld int32 dummyCSharp.MyClass::myInt
IL_0007: ldarg.0
IL_0008: call instance void [mscorlib]System.Object::.ctor()
IL_000d: nop
IL_000e: ret
} // end of method MyClass::.ctor
Note the ldc.i4.7
and stfld int32 dummyCSharp.MyClass::myInt
seems to be instructions to set the default values for the myInt field.
So such assignment is actually compiled as an additional assignment statement in a constructor.
To detect such assignment, then you will need reflection to reflect on the IL of MyClass's constructor method and look for stfld
(set fields?) commands.
EDIT: If I add some assignment into the constructor explicitly:
class MyClass
{
public int myInt = 7;
public int myOtherInt;
public MyClass()
{
myOtherInt = 8;
}
}
When I load it up in ILDASM, I got this:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 24 (0x18)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.7
IL_0002: stfld int32 dummyCSharp.MyClass::myInt
IL_0007: ldarg.0
IL_0008: call instance void [mscorlib]System.Object::.ctor()
IL_000d: nop
IL_000e: nop
IL_000f: ldarg.0
IL_0010: ldc.i4.8
IL_0011: stfld int32 dummyCSharp.MyClass::myOtherInt
IL_0016: nop
IL_0017: ret
} // end of method MyClass::.ctor
Note that the extra assigment on myOtherInt that I added was addded after a call the Object class's constructor.
IL_0008: call instance void [mscorlib]System.Object::.ctor()
So there you have it,
Any assignment done before the call to Object class's constructor in IL is a default value assignment.
Anything following it is a statement inside the class's actual constructor code.
More extensive test should be done though.
p.s. that was fun :-)
回答2:
You might want to consider a nullable int for this behavior:
class MyClass
{
int? myInt = 7;
int? myOtherInt = null;
}
回答3:
A default value is a value like any other. There is no way to differentiate between these two cases:
int explicitly = 0;
int implicitly;
In both cases, you give them the value 0, one way just saves you typing. There is no magic "default uninitialized value" - they are both zero. They work out to be exactly the same. However, the fact that you are even contemplating this indicates that you are seriously off the track of good ideas. What are you doing? What is your specific need? You are asking the wrong question ;)
回答4:
Here's what I'd do if I wanted to build this as a general runtime feature. For scalar types, I'd create a default value attribute and use that to determine defaulticity.
Here's a partial solution to the task - I'm sure it could be better, but I just knocked it out:
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Linq;
using System.Data;
namespace FieldAttribute
{
[global::System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
sealed class DefaultValueAttribute : Attribute
{
public DefaultValueAttribute(int i)
{
IntVal = i;
}
public DefaultValueAttribute(bool b)
{
BoolVal = b;
}
public int IntVal { get; set; }
public bool BoolVal { get; set; }
private static FieldInfo[] GetAttributedFields(object o, string matchName)
{
Type t = o.GetType();
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return fields.Where(fi => ((matchName != null && fi.Name == matchName) || matchName == null) &&
(fi.GetCustomAttributes(false).Where(attr => attr is DefaultValueAttribute)).Count() > 0).ToArray();
}
public static void SetDefaultFieldValues(object o)
{
FieldInfo[] fields = GetAttributedFields(o, null);
foreach (FieldInfo fi in fields)
{
IEnumerable<object> attrs = fi.GetCustomAttributes(false).Where(attr => attr is DefaultValueAttribute);
foreach (Attribute attr in attrs)
{
DefaultValueAttribute def = attr as DefaultValueAttribute;
Type fieldType = fi.FieldType;
if (fieldType == typeof(Boolean))
{
fi.SetValue(o, def.BoolVal);
}
if (fieldType == typeof(Int32))
{
fi.SetValue(o, def.IntVal);
}
}
}
}
public static bool HasDefaultValue(object o, string fieldName)
{
FieldInfo[] fields = GetAttributedFields(o, null);
foreach (FieldInfo fi in fields)
{
IEnumerable<object> attrs = fi.GetCustomAttributes(false).Where(attr => attr is DefaultValueAttribute);
foreach (Attribute attr in attrs)
{
DefaultValueAttribute def = attr as DefaultValueAttribute;
Type fieldType = fi.FieldType;
if (fieldType == typeof(Boolean))
{
return (Boolean)fi.GetValue(o) == def.BoolVal;
}
if (fieldType == typeof(Int32))
{
return (Int32)fi.GetValue(o) == def.IntVal;
}
}
}
return false;
}
}
class Program
{
[DefaultValue(3)]
int foo;
[DefaultValue(true)]
bool b;
public Program()
{
DefaultValueAttribute.SetDefaultFieldValues(this);
Console.WriteLine(b + " " + foo);
Console.WriteLine("b has default value? " + DefaultValueAttribute.HasDefaultValue(this, "b"));
foo = 2;
Console.WriteLine("foo has default value? " + DefaultValueAttribute.HasDefaultValue(this, "foo"));
}
static void Main(string[] args)
{
Program p = new Program();
}
}
}
回答5:
For value types using a nullable type for optional parameters should work. Strings could also be initialised to empty if they are not optional.
int mandatoryInt;
int? optionalInt;
However this does strike me as a bit dirty, I would stick with attributes as a clear way of doing this.
回答6:
May be this is not the simplest solution...
You can use de DefaultValue attribute to set the value like:
Import System.ComponentModel and System.Reflection
private int myNumber = 3;
[System.ComponentModel.DefaultValue(3)]
public int MyNumber
{
get
{
return myNumber;
}
set
{
myNumber = value;
}
}
And then recover the default value with reflection:
PropertyInfo prop = this.GetType().GetProperty("MyNumber");
MessageBox.Show(((DefaultValueAttribute)(prop.GetCustomAttributes(typeof(DefaultValueAttribute), true).GetValue(0))).Value.ToString());
回答7:
What about making a generic struct that contains a value and an initialized flag?
public struct InitializationKnown<T> {
private T m_value;
private bool m_initialized;
// the default constructor leaves m_initialized = false, m_value = default(T)
// InitializationKnown() {}
InitializationKnown(T value) : m_value(value), m_initialized(true) {}
public bool initialized {
get { return m_initialized; }
}
public static operator T (InitializationKnown that) {
return that.m_value;
}
// ... other operators including assignment go here
}
Then just use this in place of the members you need to know about the initialization of. Its a pretty basic variation on a lazy future or promise.
回答8:
This approach uses the property get/set process:
class myClass
{
#region Property: MyInt
private int _myIntDefault = 7;
private bool _myIntChanged = false;
private int _myInt;
private int MyInt
{
get
{
if (_myIntChanged)
{
return _myInt;
}
else
{
return _myIntDefault;
}
}
set
{
_myInt = value;
_myIntChanged = true;
}
}
private bool MyIntIsDefault
{
get
{
if (_myIntChanged)
{
return (_myInt == _myIntDefault);
}
else
{
return true;
}
}
}
#endregion
}
That's alot of code for one field - hello snippets!
回答9:
You could wrap the fields in private/protected properties. If you want to know if its been set or not, check the private field (e.g. _myInt.HasValue()).
class MyClass
{
public MyClass()
{
myInt = 7;
}
int? _myInt;
protected int myInt
{
set { _myInt = value; }
get { return _myInt ?? 0; }
}
int? _myOtherInt;
protected int myOtherInt
{
set { _myOtherInt = value; }
get { return _myOtherInt ?? 0; }
}
}
回答10:
If what you want is this, then check out the code at the bottom.
It's written in Oxygene[1], hope that's not a problem.
[1]or Delphi Prism how it's called now
var inst1 := new Sample();
var inst2 := new Sample(X := 2);
var test1 := new DefaultValueInspector<Sample>(true);
var test2 := new DefaultValueInspector<Sample>(inst2, true);
var d := test1.DefaultValueByName["X"];
var inst1HasDefault := test1.HasDefaultValue(inst1, "X");
var inst2HasDefault := test1.HasDefaultValue(inst2, "X");
Console.WriteLine("Value: {0}; inst1HasDefault: {1}; inst2HasDefault {2}",
d, inst1HasDefault, inst2HasDefault);
d := test2.DefaultValueByName["X"];
inst1HasDefault := test2.HasDefaultValue(inst1, "X");
inst2HasDefault := test2.HasDefaultValue(inst2, "X");
Console.WriteLine("Value: {0}; inst1HasDefault: {1}; inst2HasDefault {2}",
d, inst1HasDefault, inst2HasDefault);
Output:
Value: 1; inst1HasDefault: True; inst2HasDefault False Value: 2; inst1HasDefault: False; inst2HasDefault True
uses
System.Collections.Generic,
System.Reflection;
type
DefaultValueInspector<T> = public class
private
method get_DefaultValueByName(memberName : String): Object;
method get_DefaultValueByMember(memberInfo : MemberInfo) : Object;
protected
class method GetMemberErrorMessage(memberName : String) : String;
method GetMember(memberName : String) : MemberInfo;
property MembersByName : Dictionary<String, MemberInfo>
:= new Dictionary<String, MemberInfo>(); readonly;
property GettersByMember : Dictionary<MemberInfo, Converter<T, Object>>
:= new Dictionary<MemberInfo, Converter<T, Object>>(); readonly;
property DefaultValuesByMember : Dictionary<MemberInfo, Object>
:= new Dictionary<MemberInfo, Object>(); readonly;
public
property UseHiddenMembers : Boolean; readonly;
property DefaultValueByName[memberName : String] : Object
read get_DefaultValueByName;
property DefaultValueByMember[memberInfo : MemberInfo] : Object
read get_DefaultValueByMember;
method GetGetMethod(memberName : String) : Converter<T, Object>;
method GetGetMethod(memberInfo : MemberInfo) : Converter<T, Object>;
method HasDefaultValue(instance : T; memberName : String) : Boolean;
method HasDefaultValue(instance : T; memberInfo : MemberInfo) : Boolean;
constructor(useHiddenMembers : Boolean);
constructor(defaultInstance : T; useHiddenMembers : Boolean);
end;
implementation
constructor DefaultValueInspector<T>(useHiddenMembers : Boolean);
begin
var ctorInfo := typeOf(T).GetConstructor([]);
constructor(ctorInfo.Invoke([]) as T, useHiddenMembers);
end;
constructor DefaultValueInspector<T>(defaultInstance : T; useHiddenMembers : Boolean);
begin
var bf := iif(useHiddenMembers,
BindingFlags.NonPublic)
or BindingFlags.Public
or BindingFlags.Instance;
for mi in typeOf(T).GetMembers(bf) do
case mi.MemberType of
MemberTypes.Field :
with matching fi := FieldInfo(mi) do
begin
MembersByName.Add(fi.Name, fi);
GettersByMember.Add(mi, obj -> fi.GetValue(obj));
end;
MemberTypes.Property :
with matching pi := PropertyInfo(mi) do
if pi.GetIndexParameters().Length = 0 then
begin
MembersByName.Add(pi.Name, pi);
GettersByMember.Add(mi, obj -> pi.GetValue(obj, nil));
end;
end;
for g in GettersByMember do
with val := g.Value(DefaultInstance) do
if assigned(val) then
DefaultValuesByMember.Add(g.Key, val);
end;
class method DefaultValueInspector<T>.GetMemberErrorMessage(memberName : String) : String;
begin
exit "The member '" + memberName + "' does not exist in type " + typeOf(T).FullName
+ " or it has indexers."
end;
method DefaultValueInspector<T>.get_DefaultValueByName(memberName : String): Object;
begin
var mi := GetMember(memberName);
DefaultValuesByMember.TryGetValue(mi, out result);
end;
method DefaultValueInspector<T>.get_DefaultValueByMember(memberInfo : MemberInfo) : Object;
begin
if not DefaultValuesByMember.TryGetValue(memberInfo, out result) then
raise new ArgumentException(GetMemberErrorMessage(memberInfo.Name),
"memberName");
end;
method DefaultValueInspector<T>.GetGetMethod(memberName : String) : Converter<T, Object>;
begin
var mi := GetMember(memberName);
exit GetGetMethod(mi);
end;
method DefaultValueInspector<T>.GetGetMethod(memberInfo : MemberInfo) : Converter<T, Object>;
begin
if not GettersByMember.TryGetValue(memberInfo, out result) then
raise new ArgumentException(GetMemberErrorMessage(memberInfo.Name),
"memberName");
end;
method DefaultValueInspector<T>.GetMember(memberName : String) : MemberInfo;
begin
if not MembersByName.TryGetValue(memberName, out result) then
raise new ArgumentException(GetMemberErrorMessage(memberName),
"memberName");
end;
method DefaultValueInspector<T>.HasDefaultValue(instance : T; memberName : String) : Boolean;
begin
var getter := GetGetMethod(memberName);
var instanceValue := getter(instance);
exit Equals(DefaultValueByName[memberName], instanceValue);
end;
method DefaultValueInspector<T>.HasDefaultValue(instance : T; memberInfo : MemberInfo) : Boolean;
begin
var getter := GetGetMethod(memberInfo);
var instanceValue := getter(instance);
exit Equals(DefaultValueByMember[memberInfo], instanceValue);
end;
回答11:
The compiler can be set to generate a warning if you try to use a variable before assigning it a value. I have the default setting and that how it behave.
回答12:
Does the following help:
bool isAssigned = (myOtherInt == default(int));
来源:https://stackoverflow.com/questions/271904/can-you-detect-if-a-c-sharp-field-has-been-assigned-a-default-value