Können Sie erkennen, ob eine C # Feld einen Standardwert zugewiesen wurde?
-
07-07-2019 - |
Frage
Sagen Sie bitte eine Klassendeklaration haben, z.
class MyClass
{
int myInt=7;
int myOtherInt;
}
Nun , ist es eine Möglichkeit, in generischem Code, mithilfe von Reflektion (oder anderen Mitteln, was das betrifft), dass ich ableiten kann, dass myInt einen Standardwert hat zugewiesen, während myOtherInt nicht? Beachten Sie den Unterschied zwischen mit einem expliziten Standardwert initialisiert wird, und gelassen zu werden, um es impliziter Standardwert ist (myOtherInt wird auf 0 initialisiert werden, Standardeinstellung).
Aus meiner eigenen Forschung sieht es aus wie es keine Möglichkeit, dies zu tun ist -. Aber ich dachte, dass ich, bevor er aufgibt hier fragen würde
[Bearbeiten]
Auch bei nullable und Referenztypen ich zwischen denen, distingush wollen, die als null gestellt worden sind, und diejenigen, die explizit initialisiert auf null wurden. Dies ist so, dass ich sagen kann, dass Felder mit einem initialiser sind „optional“ und andere Bereiche sind „Pflicht“. Im Moment habe ich dies mit Attributen zu tun -., Die mich mit ihrer Redundanz von Informationen, die in diesem Fall Zipperlein
Lösung
ich Ihren Code kompiliert und laden Sie es in ILDASM und bekam diese
.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
Beachten Sie die ldc.i4.7
und stfld int32 dummyCSharp.MyClass::myInt
scheint Anweisungen zu sein, die Standardwerte für das myInt Feld zu setzen.
So eine solche Zuordnung wird tatsächlich als zusätzliche Zuweisungsanweisung in einem Konstruktor erstellt.
solche Zuordnung zu erkennen, dann werden Sie Reflexion müssen auf die IL von MyClass Konstruktor-Methode zu reflektieren und sucht stfld
(Set-Felder?) Befehle.
EDIT: Wenn ich eine Zuordnung in den Konstruktor hinzufügen explizit:
class MyClass
{
public int myInt = 7;
public int myOtherInt;
public MyClass()
{
myOtherInt = 8;
}
}
Wenn ich es in ILDASM laden, ich habe dies:
.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
Beachten Sie, dass die zusätzliche assigment auf myOtherInt, die ich hinzugefügt wurde added nach ein Aufruf der Konstruktor der Object-Klasse.
IL_0008: call instance void [mscorlib]System.Object::.ctor()
So haben Sie es,
Die Abtretung erfolgt vor der Anruf Klasse Konstruktor in IL-Objekt ist ein Standardwert Zuordnung.
Alles, was folgt es eine Anweisung innerhalb der tatsächlichen Konstruktorcode Klasse ist.
Weitere umfangreicher Test obwohl getan werden sollte.
P. S. das hat Spaß gemacht: -)
Andere Tipps
Sie möchten vielleicht ein Nullable-int für dieses Verhalten berücksichtigen:
class MyClass
{
int? myInt = 7;
int? myOtherInt = null;
}
Ein Standardwert ist ein Wert, wie jedes andere. Es gibt keine Möglichkeit, zwischen diesen beiden Fällen zu unterscheiden:
int explicitly = 0;
int implicitly;
In beiden Fällen geben Sie ihnen den Wert 0, eine Möglichkeit, Sie mit der Eingabe nur spart. Es gibt keine magische „default nicht initialisierten Wert“ - sie sind beide Null. Sie arbeiten genau gleich sein werden. Jedoch, dass Sie die Tatsache, erwägen auch dies zeigt an, dass Sie ernsthaft von der Strecke guter Ideen sind. Was machen Sie? Was ist Ihre spezifischen Bedürfnisse? Sie haben die falsche Frage;)
Hier ist, was ich tun würde, wenn ich dies als eine allgemeine Laufzeit-Funktion bauen will. Für skalare Typen, würde ich einen Standardwert Attribut erstellen und dass defaulticity zu bestimmen, verwendet werden.
Hier ist eine Teillösung für die Aufgabe - ich bin sicher, es könnte besser sein, aber ich klopfe es einfach aus:
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();
}
}
}
Für Werttypen ein Nullable Type mit für optionale Parameter funktionieren sollte. Strings könnte auch leeren initialisiert werden, wenn sie nicht sind optional.
int mandatoryInt;
int? optionalInt;
Doch dieser erstaunt ich als ein bisschen schmutzig, ich mit Attributen als klare Art und Weise halten würde, dies zu tun.
Kann sein, dies ist nicht die einfachste Lösung ...
Sie können mit de Default Attribut den Wert einzustellen wie:
Import System.ComponentModel und System.Reflection
private int myNumber = 3;
[System.ComponentModel.DefaultValue(3)]
public int MyNumber
{
get
{
return myNumber;
}
set
{
myNumber = value;
}
}
Und dann wiederherstellen den Standardwert mit Reflexion:
PropertyInfo prop = this.GetType().GetProperty("MyNumber");
MessageBox.Show(((DefaultValueAttribute)(prop.GetCustomAttributes(typeof(DefaultValueAttribute), true).GetValue(0))).Value.ToString());
Was ist eine generische Struktur zu machen, der einen Wert enthält und einen initialisierten 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
}
Dann ist diese nur verwenden anstelle der Mitglieder über die Initialisierung von Ihnen wissen müssen. Es ist eine ziemlich grundlegende Veränderung auf einer faulen Zukunft oder Versprechen.
Dieser Ansatz nutzt die Eigenschaft get / set-Prozess:
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
}
Das ist eine Menge Code für ein Feld - hallo Schnipsel
!Sie können die Felder in privaten / geschützten Eigenschaften wickeln. Wenn Sie wissen wollen, ob seine gesetzt worden ist oder nicht, überprüfen Sie den privaten Bereich (z _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; }
}
}
Wenn das, was Sie wollen, ist dies, dann am unteren Rand den Code überprüfen.
Es ist in Oxygene [1] geschrieben, hoffe, das ist kein Problem.
[1] oder Delphi Prism, wie es jetzt genannt
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);
Ausgabe:
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;
Der Compiler kann eingestellt werden, um eine Warnung zu erzeugen, wenn Sie versuchen, eine Variable zu verwenden, bevor sie einen Wert zuweisen. Ich habe die Standardeinstellung und das, wie es sich verhalten.
Ist die folgende Hilfe:
bool isAssigned = (myOtherInt == default(int));