هل يمكنك اكتشاف ما إذا تم تعيين قيمة افتراضية لحقل C#؟

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

  •  07-07-2019
  •  | 
  •  

سؤال

لنفترض أن لديك إعلانًا للفصل، على سبيل المثال:


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

الآن، هل هناك طريقة في التعليمات البرمجية العامة، باستخدام الانعكاس (أو أي وسيلة أخرى، في هذا الشأن)، يمكنني من خلالها استنتاج أن myInt لديه قيمة افتراضية معينة، في حين أن myOtherInt لا يفعل ذلك؟لاحظ الفرق بين التهيئة بقيمة افتراضية صريحة، وترك القيمة الافتراضية الضمنية (ستتم تهيئة myOtherInt إلى 0 افتراضيًا).

من خلال بحثي الخاص، يبدو أنه لا توجد طريقة للقيام بذلك - لكنني اعتقدت أنني سأسأل هنا قبل الاستسلام.

[يحرر]

حتى مع الأنواع الفارغة والمرجعية، أريد التمييز بين تلك التي تم تركها خالية، وتلك التي تمت تهيئتها بشكل صريح لتكون خالية.هذا حتى أستطيع أن أقول إن الحقول التي تحتوي على مُهيئ هي "اختيارية" والحقول الأخرى "إلزامية".في الوقت الحالي، يتعين علي القيام بذلك باستخدام السمات - الأمر الذي يزعجني بسبب تكرار المعلومات في هذه الحالة.

هل كانت مفيدة؟

المحلول

لقد قمت بتجميع الكود الخاص بك وقمت بتحميله في 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 التي أضفتها بعد استدعاء منشئ فئة الكائن.

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

إذن إليكم الأمر،

تم إنجاز أي مهمة قبل يعد استدعاء مُنشئ فئة الكائن في IL بمثابة تعيين قيمة افتراضية.

أي شيء يتبعه هو عبارة داخل كود المنشئ الفعلي للفئة.

وينبغي إجراء اختبار أكثر شمولا بالرغم من ذلك.

ملاحظة.كان هذا ممتعا :-)

نصائح أخرى

وقد ترغب في النظر في كثافة قيم الفارغة لهذا السلوك:

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

والقيمة الافتراضية هي قيمة مثل أي دولة أخرى. لا توجد وسيلة للتمييز بين هاتين الحالتين:

int explicitly = 0;
int implicitly;

في كلتا الحالتين، يجب منحهم القيمة 0، طريقة واحدة فقط يوفر عليك الكتابة. ليس هناك سحر "القيمة الافتراضية غير مهيأ" - كلاهما صفر. وهم يعملون ليكون بالضبط نفس الشيء. ومع ذلك، فإن حقيقة أن كنت تفكر حتى هذا يشير إلى أن كنت جديا خارج المسار من الأفكار الجيدة. ماذا تفعل؟ ما هي حاجة معينة؟ تسألون السؤال الخطأ.)

وإليك ما كنت تفعل لو كنت أرغب في بناء هذا كسمة وقت التشغيل العامة. لأنواع العددية، فما استقاموا لكم فاستقيموا إنشاء سمة القيمة الافتراضية واستخدام ذلك لتحديد defaulticity.

وهنا حل جزئي لهذه المهمة - أنا متأكد من أنه يمكن أن يكون أفضل، ولكن أنا فقط طرقت بها:

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

لأنواع القيمة باستخدام نوع قيم الفارغة للالمعلمات الاختيارية يجب أن تعمل. ويمكن أيضا أن initialised سلاسل لتفريغ إذا لم تكن اختيارية.

int mandatoryInt;
int? optionalInt;

ولكن هذا لا يبدو لي أن القذرة قليلا، وأود أن العصا مع سمات وسيلة واضحة للقيام بذلك.

قد يكون هذا ليس هو أبسط حل ...

ويمكنك استخدام دي القيمة_الافتراضية السمة لتعيين قيمة مثل:

واستيراد 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; }
    }
}

إذا ما كنت تريد هذا، ثم تحقق من التعليمات البرمجية في أسفل.
هو مكتوب في الأكسجين [1]، ونأمل أن ليست مشكلة.

[1] أو دلفي بريزم كيف يطلق عليه الآن


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