views:

2157

answers:

13

Say you have a class declaration, e.g.:


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

Now, is there a way in generic code, using reflection (or any other means, for that matter), that I can deduce that myInt has a default value assigned, whereas myOtherInt does not? Note the difference between being initialised with an explicit default value, and being left to it's implicit default value (myOtherInt will be initialised to 0, by default).

From my own research it looks like there is no way to do this - but I thought I'd ask here before giving up.

[Edit]

Even with nullable and reference types I want to distingush between those that have been left as null, and those that have been explicitly initialised to null. This is so that I can say that fields with an initialiser are "optional" and other fields are "mandatory". At the moment I'm having to do this using attributes - which niggles me with their redundancy of information in this case.

+2  A: 

You might want to consider a nullable int for this behavior:

class MyClass
{
  int? myInt = 7;
  int? myOtherInt = null;
}
duckworth
Good suggestion. Unfortunately I also need to deal with reference types and detect if a default of null has been provided. This is so I can distinguish between mandatory and optional values. I'm voting you up because it's a good answer, even if it's not the one I want- and adding more info to the Q.
Phil Nash
+2  A: 

A default value is a value like any other. There is no way to differentiate between these two cases:

int explicitly = 0;
int implicitly;

In both cases, you give them the value 0, one way just saves you typing. There is no magic "default uninitialized value" - they are both zero. They work out to be exactly the same. However, the fact that you are even contemplating this indicates that you are seriously off the track of good ideas. What are you doing? What is your specific need? You are asking the wrong question ;)

Sander
I know what you mean. However I think I do have a valid use case.I'm using reflection to populate values from command line arguments, app.config, or even other sources. See my question update for more details. Using attributes works, but has a level of redundancy built in that I'd like to eliminate
Phil Nash
A: 

The compiler can be set to generate a warning if you try to use a variable before assigning it a value. I have the default setting and that how it behave.

Hapkido
+12  A: 

I compiled your code and load it up in ILDASM and got this

.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

Note the ldc.i4.7 and stfld int32 dummyCSharp.MyClass::myInt seems to be instructions to set the default values for the myInt field.

So such assignment is actually compiled as an additional assignment statement in a constructor.

To detect such assignment, then you will need reflection to reflect on the IL of MyClass's constructor method and look for stfld (set fields?) commands.


EDIT: If I add some assignment into the constructor explicitly:

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

    public MyClass()
    {
        myOtherInt = 8;
    }
}

When I load it up in ILDASM, I got this:

.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

Note that the extra assigment on myOtherInt that I added was addded after a call the Object class's constructor.

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

So there you have it,

Any assignment done before the call to Object class's constructor in IL is a default value assignment.

Anything following it is a statement inside the class's actual constructor code.

More extensive test should be done though.

p.s. that was fun :-)

chakrit
Nasty, but could be something in this - thanks. Will have to look deeper (I haven't done any reflection on IL as yet).
Phil Nash
Responding to your update: Cool - looks even more well defined.
Phil Nash
I supposed got too much time on my hands. Gotta get to work haha.
chakrit
Good post, but still .. i'd go with nullable types int? will do what is needed :)
Tigraine
Not so Tigraine - see my question update
Phil Nash
A: 

Does the following help:

bool isAssigned = (myOtherInt == default(int));
huseyint
No, sorry. Nice try. In this case default(int) will be 0, which I could have initialised it to explicitly.
Phil Nash
+1  A: 

For value types using a nullable type for optional parameters should work. Strings could also be initialised to empty if they are not optional.

int mandatoryInt;
int? optionalInt;

However this does strike me as a bit dirty, I would stick with attributes as a clear way of doing this.

Jack Ryan
Thanks - but see my response to duckworth, and my question update.
Phil Nash
A: 

This approach uses the property 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
    }

That's alot of code for one field - hello snippets!

Doug L.
A: 

May be this is not the simplest solution...

You can use de DefaultValue attribute to set the value like:

Import System.ComponentModel and System.Reflection

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

And then recover the default value with reflection:

PropertyInfo prop = this.GetType().GetProperty("MyNumber");
MessageBox.Show(((DefaultValueAttribute)(prop.GetCustomAttributes(typeof(DefaultValueAttribute), true).GetValue(0))).Value.ToString());
Javier Suero Santos
Yeah, that's what I'm already doing. Just wanted to reduce the redundancy
Phil Nash
d'oh. Just added a somewhat more generic solution that reimplements DefaultValueAttribute
plinth
heh, at least you've learnt something :-)
Phil Nash
A: 

You could wrap the fields in private/protected properties. If you want to know if its been set or not, check the private field (e.g. _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; }
 }
}
dviljoen
+1  A: 

What about making a generic struct that contains a value and an initialized flag?

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
}

Then just use this in place of the members you need to know about the initialization of. Its a pretty basic variation on a lazy future or promise.

Edward Kmett
On first sight this actually isn't bad. However, I don't think it adds much to the attribute technique - it just moves the "meta-data" from the attribute to the wrapper class - and adds more complexity.I'll vote you up for making a workable suggestion, but probably won't go that route. Thanks.
Phil Nash
A: 

Here's what I'd do if I wanted to build this as a general runtime feature. For scalar types, I'd create a default value attribute and use that to determine defaulticity.

Here's a partial solution to the task - I'm sure it could be better, but I just knocked it out:

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();
        }
    }
}
plinth
Yeah, that's basically what I already have. Thanks for going to the trouble of knocking that up. However, the reflecting over attributes side of it is fine. I just wanted to see if I could even eliminate that.
Phil Nash
Strictly speaking, I currently set the defauly using C# syntax (initializer), but have an attribute to mark whether the field is optional or not
Phil Nash
A: 

If what you want is this, then check out the code at the bottom.
It's written in Oxygene[1], hope that's not a problem.

[1]or Delphi Prism how it's called now


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;
Robert Giesecke
A: 

Huseyint's suggestion doesn't work, because you get the "Use of unassigned local variable 'sp_id'"