Question

I need to set a property inside of a class from another class that defines the first class as a property. I want to default a value inside the child class. An example of this would be:

public enum NamingConvention
{
    Name1 = 1,
    Name2
}
public class Class1
{
    public Class1()
    {
    }

    public int Id { get; set; }
    public  NamingConvention Naming{ get; set; }
}

public class Class2
{
    public Class2()
    {
    }

    public List<Class1> Name1s { get; set; }
}

public class Class3
{
    public Class2()
    {
    }

    public List<Class1> Name2s { get; set; }
}

I want to be able to put an attribute or something over the Class1 property inside of Class2 and Class3 so that in Class2, the Naming Property gets set to Name1 and and for Class3, it would be automatically set to Name2.

Hope that makes sense. I tried to make this as simple an example as possible. Any ideas out there? I am trying to avoid abstract classes because my real entities are tied to nHibernate and don't want to change the model at this time.

Was it helpful?

Solution

This can be accomplished with the use of the DefaultValueAttribute, a custom TypeConverter and Reflection. It seems unlikely this will perform better than what you are currently doing, but I'll leave that for you to evaluate.

Apply the TypeConverter attribute to Class 1

[TypeConverter(typeof(Class1Converter))]
public class Class1
{
    public int Id { get; set; }
    public NamingConvention Naming { get; set; }
}
public enum NamingConvention
{
    Name1 = 1,
    Name2,
    Name3,
    Name4
}

Define the Class1Converter. Note this simple converter only sets the value of the NamingConvention parameter.

public class Class1Converter: TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, 
                                        Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }
    public override bool CanConvertTo(ITypeDescriptorContext context,
                                      Type destinationType)
    {
        if(destinationType == typeof(Class1))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context,
                                       System.Globalization.CultureInfo culture, 
                                       object value)
    {
        var stringValue = value as string;
        if(stringValue != null)
        {
                            return new Class1
                {
                    Naming = (NamingConvention)Enum.Parse(typeof(NamingConvention), stringValue)
                };
        }
        return base.ConvertFrom(context, culture, value);
    }
}

For convenience I am declaring this in an Extension Method, it could easily be set up as part of the classes with defaults...

public static class DefaultExtension
{
    public static IEnumerable<PropertyInfo> GetProperties<T>(this Type type)
    {
        return type.GetProperties().Where(p => p.PropertyType == typeof (T));
    }
    public static void SetDefaults<T>(this T toDefault)
    {
        foreach (PropertyInfo p in toDefault.GetType().GetProperties())
        {
            foreach (var dv in p.GetCustomAttributes(true).OfType<DefaultValueAttribute>())
            {
                p.SetValue(toDefault, dv.Value, null);
            }
        }
    }
} 

Finally you declare place DefaultValue attributes on your properties. I am calling SetDefaults() from the constructors here for convenience again, in your case you would still need to call it after the instances are loaded from NHibernate.

public class Class2
{
    public int X { get; set; }
    [DefaultValue(typeof(Class1), "Name2")]
    public Class1 Name2Class { get; set; }
    public Class2()
    {
        this.SetDefaults();
    }
}

public class Class3
{
    public int Y { get; set; }
    [DefaultValue(typeof(Class1), "Name3")]
    public Class1 Name3Class { get; set; }
        public Class3()
        {
            this.SetDefaults();
        }
}

Unit test demonstrating validity...

[Test]
public void TestDefaultValueAttribute()
{
    //Class2 have Name2 as the default value for the Naming property
    var c2 = new Class2();
    Assert.That(c2,Is.Not.Null);
    Assert.That(c2.Name2Class, Is.Not.Null);
    Assert.That(c2.Name2Class.Naming, Is.EqualTo(NamingConvention.Name2));
    //Class3 have Name3 as the default value for the Naming Property
    var c3 = new Class3();
    Assert.That(c3, Is.Not.Null);
    Assert.That(c3.Name3Class, Is.Not.Null);
    Assert.That(c3.Name3Class.Naming, Is.EqualTo(NamingConvention.Name3));
    //wipes out other properties of the Class1 attribute.
    // to demonstrate, set properties to something other than the default then call
    // SetDefaults again.

    c3.Name3Class.Naming = NamingConvention.Name1;
    c3.Name3Class.Id = 10;
    c3.SetDefaults();
    Assert.That(c3.Name3Class.Id, Is.EqualTo(0));
    Assert.That(c3.Name3Class.Naming, Is.EqualTo(NamingConvention.Name3));
}

You will notice that this wipes out the Id property of Class1 If this is not desired, you could come up with a more targeted version of SetDefaults that only overwrote specific properties of Class1. At this point I don't know if I would really continue using DefaultValue, as use case deviates from the original and using this in combination with the above method would produce unexpected results. I would probably write a custom 'DefaultNaminingConventionAttribute for this purpose.

public static void SetDefaultNamingConvention<T>(this T toDefault)
{
    foreach (PropertyInfo p in toDefault.GetType().GetProperties<Class1>())
    {
        foreach (var dv in p.GetCustomAttributes(true).OfType<DefaultValueAttribute>())
        {
            var pValue = p.GetValue(toDefault, null) as Class1;
            if (pValue != null)
            {
                pValue.Naming = ((Class1)dv.Value).Naming;
            }
            else
            {
                p.SetValue(toDefault, dv.Value, null);
            }
        }
    }
}

[Test]
public void SetDefaultNamingConventionDefaultShouldOnlyDefaultNamingProperty()
{
    var c3 = new Class3();
    c3.Name3Class.Naming = NamingConvention.Name1;
    c3.Name3Class.Id = 20;
    c3.SetDefaultNamingConvention();
    Assert.That(c3.Name3Class.Id, Is.EqualTo(20));
    Assert.That(c3.Name3Class.Naming, Is.EqualTo(NamingConvention.Name3));
}

EDIT: Updated to deal with setting defaults for list members
With this new SetListDefaults extension method, we now can apply the default to members of List<Class1>. Here I would almost definitely no longer use DefaultValue, but would define a custom attribute for use with collections. This is beyond the scope of the question though.

public static class DefaultExtension
{
    public static IEnumerable<PropertyInfo> GetProperties<T>(this Type type)
    {
        return type.GetProperties().Where(p => p.PropertyType == typeof (T));
    }
   public static void SetListDefaults<T>(this T toDefault)
    {
        foreach (PropertyInfo p in toDefault.GetType().GetProperties<List<Class1>>())
        {
            foreach (var dv in p.GetCustomAttributes(true).OfType<DefaultValueAttribute>())
            {
                var pValue = p.GetValue(toDefault, null) as List<Class1>;
                if (pValue != null)
                {
                    foreach (var class1 in pValue)
                    {
                        class1.Naming = ((Class1) dv.Value).Naming;
                    }
                }
            }
        }
    }
}

Now provided a class with a List property...

public class Class4
{
    public int Z { get; set; }
    [DefaultValue(typeof (Class1), "Name4")]
    public List<Class1> Name4Classes { get; set; }
}

And a unit test to verify only the Naming Property of each item in the list is modified.

[Test]
public void SetListDefaultsShouldResetNamingConventionOfEachListMember()
{
    var c4 = new Class4
        {
            Z = 100,
            Name4Classes = new List<Class1>
                {
                    new Class1 {Id = 1, Naming = NamingConvention.Name1},
                    new Class1 {Id = 2, Naming = NamingConvention.Name2},
                    new Class1 {Id = 3, Naming = NamingConvention.Name3}
                }
        };
    Assert.That(c4.Name4Classes, Is.Not.Empty);
    Assert.That(c4.Name4Classes.Count, Is.EqualTo(3));
    Assert.That(c4.Name4Classes.Any(c => c.Id == 0), Is.False);
    Assert.That(c4.Name4Classes.Any(c => c.Naming == NamingConvention.Name4), Is.False);
    c4.SetListDefaults();
    Assert.That(c4.Name4Classes, Is.Not.Empty);
    Assert.That(c4.Name4Classes.Count, Is.EqualTo(3));
    Assert.That(c4.Name4Classes.Any(c=> c.Id == 0), Is.False);
    Assert.That(c4.Name4Classes.All(c=> c.Naming == NamingConvention.Name4), Is.True);
}

OTHER TIPS

I would use the constructors.

In Class2's constructor:

public Class2()
{
    Name1Class = new Class1()
    Name1Class.Naming = NamingConvention.Name1
}

In Class3's Constructor:

    public Class3()
    {
      Name2Class = new Class1()
      Name2Class.Naming = NamingConvention.Name2
    }

If you want to get fancy you could put a parameter on the constructor in Class1 to allow you to set Naming when the object is created.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top