هل يمكنك اكتشاف ما إذا تم تعيين قيمة افتراضية لحقل C#؟
-
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));