Riesci a rilevare se a un campo C # è stato assegnato un valore predefinito?
-
07-07-2019 - |
Domanda
Supponi di avere una dichiarazione di classe, ad esempio:
class MyClass
{
int myInt=7;
int myOtherInt;
}
Ora, c'è un modo nel codice generico, usando reflection (o qualsiasi altro mezzo, per quella materia), che posso dedurre che myInt ha un valore predefinito assegnato, mentre myOtherInt no? Nota la differenza tra essere inizializzato con un valore predefinito esplicito ed essere lasciato al suo valore predefinito implicito (myOtherInt verrà inizializzato su 0, per impostazione predefinita).
Dalla mia ricerca sembra che non ci sia modo di farlo - ma ho pensato di chiedere qui prima di arrendermi.
[Modifica]
Anche con i tipi nullable e di riferimento voglio distinguere tra quelli che sono stati lasciati come null e quelli che sono stati esplicitamente inizializzati su null. Questo è così che posso dire che i campi con un inizializzatore sono "opzionali" e altri campi sono "obbligatori". Al momento devo farlo utilizzando gli attributi, il che mi confonde con la loro ridondanza di informazioni in questo caso.
Soluzione
Ho compilato il tuo codice e l'ho caricato in ILDASM e l'ho preso
.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
Nota ldc.i4.7
e stfld int32 dummyCSharp.MyClass :: myInt
sembra essere istruzioni per impostare i valori predefiniti per il campo myInt.
Quindi tale assegnazione viene effettivamente compilata come un'istruzione di assegnazione aggiuntiva in un costruttore.
Per rilevare tale assegnazione, dovrai riflettere per riflettere sull'IL del metodo di costruzione di MyClass e cercare i comandi stfld
(imposta i campi?).
EDIT: se aggiungo esplicitamente un compito nel costruttore:
class MyClass
{
public int myInt = 7;
public int myOtherInt;
public MyClass()
{
myOtherInt = 8;
}
}
Quando lo carico in ILDASM, ottengo questo:
.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
Nota che l'assegnazione aggiuntiva su myOtherInt che ho aggiunto è stata aggiunta dopo una chiamata al costruttore della classe Object.
IL_0008: call instance void [mscorlib]System.Object::.ctor()
Quindi il gioco è fatto,
Qualsiasi assegnazione eseguita prima della chiamata al costruttore della classe Object in IL è un'assegnazione di valore predefinita.
Tutto ciò che segue è un'istruzione all'interno del codice costruttore effettivo della classe.
Dovrebbero essere eseguiti test più approfonditi.
P.S. è stato divertente :-)
Altri suggerimenti
Potresti voler considerare un int nullable per questo comportamento:
class MyClass
{
int? myInt = 7;
int? myOtherInt = null;
}
Un valore predefinito è un valore come un altro. Non è possibile distinguere tra questi due casi:
int explicitly = 0;
int implicitly;
In entrambi i casi, dai loro il valore 0, in un modo solo ti salva la digitazione. Non esiste alcun valore magico "non inizializzato predefinito" - sono entrambi zero. Lavorano per essere esattamente gli stessi. Tuttavia, il fatto che tu stia anche contemplando questo indica che sei seriamente fuori strada dalle buone idee. Cosa stai facendo? Qual è il tuo bisogno specifico? Stai ponendo la domanda sbagliata;)
Ecco cosa farei se volessi crearlo come funzionalità generale di runtime. Per i tipi scalari, creerei un attributo di valore predefinito e lo userei per determinare il defaulticity.
Ecco una soluzione parziale all'attività: sono sicuro che potrebbe essere migliore, ma l'ho appena eliminato:
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();
}
}
}
Per i tipi di valore che utilizzano un tipo nullable per i parametri opzionali dovrebbe funzionare. Le stringhe possono anche essere inizializzate per svuotarsi se non sono opzionali.
int mandatoryInt;
int? optionalInt;
Comunque questo mi sembra un po 'sporco, mi atterrerei con gli attributi come un modo chiaro per farlo.
Forse questa non è la soluzione più semplice ...
Puoi usare l'attributo de DefaultValue per impostare il valore come:
Importa System.ComponentModel e System.Reflection
private int myNumber = 3;
[System.ComponentModel.DefaultValue(3)]
public int MyNumber
{
get
{
return myNumber;
}
set
{
myNumber = value;
}
}
E quindi ripristinare il valore predefinito con reflection:
PropertyInfo prop = this.GetType().GetProperty("MyNumber");
MessageBox.Show(((DefaultValueAttribute)(prop.GetCustomAttributes(typeof(DefaultValueAttribute), true).GetValue(0))).Value.ToString());
Che ne dici di creare una struttura generica che contenga un valore e un flag inizializzato?
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
}
Quindi usa questo al posto dei membri che devi sapere sull'inizializzazione di. È una variazione piuttosto semplice di un futuro o una promessa pigri.
Questo approccio utilizza il processo get / set della proprietà:
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
}
Questo è un sacco di codice per un campo: ciao snippet!
Puoi avvolgere i campi in proprietà private / protette. Se vuoi sapere se è stato impostato o meno, controlla il campo privato (ad esempio _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; }
}
}
Se quello che vuoi è questo, controlla il codice in basso.
È scritto in Oxygene [1], spero che non sia un problema.
[1] o Delphi Prism come si chiama ora
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;
Il compilatore può essere impostato per generare un avviso se si tenta di utilizzare una variabile prima di assegnargli un valore. Ho l'impostazione predefinita e questo come si comporta.
Fa il seguente aiuto:
bool isAssigned = (myOtherInt == default(int));