Можете ли вы определить, присвоено ли полю C# значение по умолчанию?

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

  •  07-07-2019
  •  | 
  •  

Вопрос

Предположим, у вас есть объявление класса, например:


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

Теперь, есть ли способ в общем коде, используя отражение (или любые другие средства, если на то пошло), я могу сделать вывод, что myInt имеет назначенное значение по умолчанию, а myOtherInt — нет?Обратите внимание на разницу между инициализацией с явным значением по умолчанию и сохранением неявного значения по умолчанию (по умолчанию myOtherInt будет инициализирован значением 0).

Судя по моим собственным исследованиям, похоже, что нет никакого способа сделать это, но я решил спросить здесь, прежде чем сдаваться.

[Редактировать]

Даже при использовании типов, допускающих значение NULL, и ссылочных типов я хочу различать те, для которых было оставлено значение NULL, и те, которые были явно инициализированы значением NULL.Это значит, что я могу сказать, что поля с инициализатором являются «необязательными», а другие поля — «обязательными».На данный момент мне приходится делать это с помощью атрибутов, что в данном случае меня смущает из-за их избыточности информации.

Это было полезно?

Решение

Я скомпилировал ваш код, загрузил его в ILDASM и получил вот это

.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

Обратите внимание ldc.i4.7 и stfld int32 dummyCSharp.MyClass::myInt кажется, это инструкции по установке значений по умолчанию для поля myInt.

Таким образом, такое присваивание фактически компилируется как дополнительный оператор присваивания в конструкторе.

Чтобы обнаружить такое назначение, вам понадобится рефлексия, чтобы отразить IL метода конструктора MyClass и поискать stfld (установить поля?) команды.


РЕДАКТИРОВАТЬ:Если я явно добавлю какое-то задание в конструктор:

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

    public MyClass()
    {
        myOtherInt = 8;
    }
}

Когда я загружаю его в ILDASM, я получаю следующее:

.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

Обратите внимание, что добавленное мной дополнительное назначение для myOtherInt было добавлено. после вызов конструктора класса Object.

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

Итак, вот оно,

Любое задание выполнено до вызов конструктора класса Object в IL является присвоением значения по умолчанию.

Все, что следует за ним, является оператором внутри фактического кода конструктора класса.

Однако необходимо провести более обширное тестирование.

п.с.это было весело :-)

Другие советы

Возможно, вы захотите рассмотреть возможность использования nullable int для такого поведения:

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

Значение по умолчанию — это такое же значение, как и любое другое.Невозможно провести различие между этими двумя случаями:

int explicitly = 0;
int implicitly;

В обоих случаях вы присваиваете им значение 0, в одном случае вам просто не придется печатать.Не существует волшебного «неинициализированного значения по умолчанию» — они оба равны нулю.Они работают точно так же.Однако тот факт, что вы даже обдумываете это, указывает на то, что вы серьезно отклонились от хороших идей.Что ты делаешь?Какова ваша конкретная потребность?Вы задаете неправильный вопрос ;)

Вот что бы я сделал, если бы захотел реализовать это как общую функцию времени выполнения.Для скалярных типов я бы создал атрибут значения по умолчанию и использовал его для определения дефолтности.

Вот частичное решение задачи — уверен, могло быть и лучше, но я его просто выбил:

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

Для типов значений должно работать использование типа, допускающего значение NULL, для необязательных параметров.Строки также могут быть инициализированы как пустые, если они не являются необязательными.

int mandatoryInt;
int? optionalInt;

Однако мне это кажется немного грязным, я бы придерживался атрибутов как четкого способа сделать это.

Возможно это не самое простое решение...

Вы можете использовать атрибут DefaultValue, чтобы установить значение, например:

Импортируйте System.ComponentModel и System.Reflection.

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

А затем восстановите значение по умолчанию с помощью отражения:

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

А как насчет создания общей структуры, содержащей значение и инициализированный флаг?

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
}

Затем просто используйте это вместо членов, которые вам нужно знать об инициализации.Это довольно простой вариант ленивого будущего или обещания.

Этот подход использует процесс получения/установки свойства:

    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
    }

Для одного поля слишком много кода — здравствуйте, фрагменты!

Вы можете обернуть поля в частные/защищенные свойства.Если вы хотите узнать, установлено оно или нет, проверьте личное поле (например,_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; }
    }
}

Если вы хотите именно этого, проверьте код внизу.
Он написан на Oxygene[1], надеюсь, это не проблема.

[1]или Delphi Prism, как она сейчас называется


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

Выход:

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;

Компилятор можно настроить на выдачу предупреждения, если вы попытаетесь использовать переменную до присвоения ей значения.У меня есть настройки по умолчанию и вот как они себя ведут.

Помогает ли следующее:

bool isAssigned = (myOtherInt == default(int));
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top