Pouvez-vous détecter si une valeur par défaut a été attribuée à un champ C #?

StackOverflow https://stackoverflow.com/questions/271904

  •  07-07-2019
  •  | 
  •  

Question

Supposons que vous ayez une déclaration de classe, par exemple:


class MyClass
{
  int myInt=7;
  int myOtherInt;
}

Maintenant, y a-t-il un moyen dans le code générique, utilisant la réflexion (ou tout autre moyen, d'ailleurs), de déduire que myInt a une valeur par défaut attribuée, alors que myOtherInt ne le fait pas? Notez la différence entre être initialisé avec une valeur implicite explicite et être laissé à sa valeur implicite implicite (myOtherInt sera initialisé à 0 par défaut).

D'après mes propres recherches, il semble qu'il n'y ait aucun moyen de le faire - mais je pensais que je demanderais ici avant d'abandonner.

[Modifier]

Même avec les types nullable et reference, je souhaite faire la distinction entre ceux qui ont été laissés comme étant null et ceux qui ont été explicitement initialisés à null. C’est pour que je puisse dire que les champs avec un initialiseur sont "optionnels". et les autres champs sont "obligatoires". Pour le moment, je dois le faire en utilisant des attributs, ce qui me perturbe par la redondance des informations dans ce cas.

Était-ce utile?

La solution

J'ai compilé votre code et le charge dans ILDASM et je l’ai obtenu

.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

Notez que ldc.i4.7 et stfld int32 dummyCSharp.MyClass :: myInt semblent être des instructions pour définir les valeurs par défaut du champ myInt.

Cette affectation est donc réellement compilée en tant qu'instruction d'affectation supplémentaire dans un constructeur.

Pour détecter cette affectation, vous devrez réfléchir à la méthode de construction de la méthode constructeur de MyClass et rechercher les commandes stfld (définir les champs?).

EDIT: si j'ajoute explicitement une affectation au constructeur:

class MyClass
{
    public int myInt = 7;
    public int myOtherInt;

    public MyClass()
    {
        myOtherInt = 8;
    }
}

Quand je le charge dans ILDASM, j'ai ceci:

.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

Notez que l'affectation supplémentaire sur myOtherInt que j'ai ajoutée a été ajoutée après après l'appel du constructeur de la classe Object.

IL_0008:  call       instance void [mscorlib]System.Object::.ctor()

Alors voilà,

Toute affectation effectuée avant l'appel du constructeur de la classe Object dans IL est une attribution de valeur par défaut.

Tout ce qui suit est une instruction à l'intérieur du code constructeur de la classe.

Un test plus approfondi devrait cependant être effectué.

p.s. c'était amusant: -)

Autres conseils

Vous pouvez envisager un int nullable pour ce comportement:

class MyClass
{
  int? myInt = 7;
  int? myOtherInt = null;
}

Une valeur par défaut est une valeur comme une autre. Il n’existe aucun moyen de différencier ces deux cas:

int explicitly = 0;
int implicitly;

Dans les deux cas, vous leur attribuez la valeur 0. Un sens vous évite de taper. Il n'y a pas de magie " valeur non initialisée par défaut " - Ils sont tous deux zéro. Ils travaillent pour être exactement les mêmes. Cependant, le fait que vous envisagiez même cela indique que vous êtes sérieusement hors de la piste des bonnes idées. Que faites-vous? Quel est votre besoin spécifique? Vous posez la mauvaise question;)

Voici ce que je ferais si je voulais en faire une fonctionnalité d'exécution générale. Pour les types scalaires, je créerais un attribut de valeur par défaut et l’utiliserais pour déterminer le caractère par défaut.

Voici une solution partielle à la tâche - je suis sûr que cela pourrait être mieux, mais je l'ai tout simplement assommé:

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();
        }
    }
}

Pour les types de valeur utilisant un type nullable pour les paramètres facultatifs, cela devrait fonctionner. Les chaînes peuvent également être initialisées pour se vider si elles ne sont pas facultatives.

int mandatoryInt;
int? optionalInt;

Cependant, cela me semble un peu sale, je voudrais rester avec des attributs comme un moyen clair de le faire.

Peut-être que ce n’est pas la solution la plus simple ...

Vous pouvez utiliser l'attribut de DefaultValue pour définir une valeur telle que:

Importer System.ComponentModel et System.Reflection

private int myNumber = 3;
[System.ComponentModel.DefaultValue(3)]
public int MyNumber
{
    get
    {
        return myNumber;
    }
    set
    {
        myNumber = value;
    }
}

Puis récupérez la valeur par défaut avec réflexion:

PropertyInfo prop = this.GetType().GetProperty("MyNumber");
MessageBox.Show(((DefaultValueAttribute)(prop.GetCustomAttributes(typeof(DefaultValueAttribute), true).GetValue(0))).Value.ToString());

Qu'en est-il de la création d'une structure générique contenant une valeur et un indicateur initialisé?

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
}

Utilisez simplement ceci à la place des membres dont vous avez besoin de connaître l’initialisation de. C’est une variante assez fondamentale d’un futur paresseux ou d’une promesse.

Cette approche utilise la propriété 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
    }

C'est beaucoup de code pour un champ - bonjour des extraits!

Vous pouvez envelopper les champs dans des propriétés privées / protégées. Si vous voulez savoir s'il a été défini ou non, vérifiez le champ privé (par exemple, _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; }
    }
}

Si c'est ce que vous voulez, consultez le code en bas.
C'est écrit dans Oxygene [1], espérons que ce n'est pas un problème.

[1] ou Delphi Prism comment on l'appelle maintenant


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);

Sortie:

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;

Le compilateur peut être configuré pour générer un avertissement si vous essayez d'utiliser une variable avant de lui affecter une valeur. J'ai le réglage par défaut et comment il se comporte.

Est-ce que l'aide suivante:

bool isAssigned = (myOtherInt == default(int));
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top