5

Given the following program:

using System;
using System.Collections.Generic;

namespace ConsoleApplication49
{
    using FooSpace;

    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<FooBase> foos = FooFactory.CreateFoos();

            foreach (var foo in foos)
            {             
                HandleFoo(foo);
            }
        }

        private static void HandleFoo(FooBase foo)
        {
            dynamic fooObject = foo;
            ApplyFooDefaults(fooObject);
        }

        private static void ApplyFooDefaults(Foo1 foo1)
        {
            foo1.Name = "Foo 1";

            Console.WriteLine(foo1);
        }

        private static void ApplyFooDefaults(Foo2 foo2)
        {
            foo2.Name        = "Foo 2";
            foo2.Description = "SomeDefaultDescription";

            Console.WriteLine(foo2);
        }

        private static void ApplyFooDefaults(Foo3 foo3)
        {
            foo3.Name    = "Foo 3";
            foo3.MaxSize = Int32.MaxValue;

            Console.WriteLine(foo3);
        }

        private static void ApplyFooDefaults(Foo4 foo4)
        {
            foo4.Name        = "Foo 4";
            foo4.MaxSize     = 99999999;
            foo4.EnableCache = true;

            Console.WriteLine(foo4);
        }

        private static void ApplyFooDefaults(FooBase unhandledFoo)
        {
            unhandledFoo.Name = "Unhandled Foo";
            Console.WriteLine(unhandledFoo);
        }
    }    
}

/////////////////////////////////////////////////////////
// Assume this namespace comes from a different assembly
namespace FooSpace
{
    ////////////////////////////////////////////////
    // these cannot be changed, assume these are 
    // from the .Net framework or some 3rd party
    // vendor outside of your ability to alter, in
    // another assembly with the only way to create
    // the objects is via the FooFactory and you
    // don't know which foos are going to be created
    // due to configuration.

    public static class FooFactory
    {
        public static IEnumerable<FooBase> CreateFoos()
        {
            List<FooBase> foos = new List<FooBase>();
            foos.Add(new Foo1());
            foos.Add(new Foo2());
            foos.Add(new Foo3());
            foos.Add(new Foo4());
            foos.Add(new Foo5());

            return foos;
        }
    }

    public class FooBase
    {
        protected FooBase() { }

        public string Name { get; set; }

        public override string ToString()
        {
            return String.Format("Type = {0}, Name=\"{1}\"", this.GetType().FullName, this.Name);
        }
    }

    public sealed class Foo1 : FooBase
    {
        internal Foo1() { }
    }

    public sealed class Foo2 : FooBase
    {
        internal Foo2() { }

        public string Description { get; set; }

        public override string ToString()
        {
            string baseString =  base.ToString();
            return String.Format("{0}, Description=\"{1}\"", baseString, this.Description);
        }
    }

    public sealed class Foo3 : FooBase
    {
        internal Foo3() { }

        public int MaxSize { get; set; }

        public override string ToString()
        {
            string baseString =  base.ToString();
            return String.Format("{0}, MaxSize={1}", baseString, this.MaxSize);
        }
    }

    public sealed class Foo4 : FooBase
    {
        internal Foo4() { }

        public int MaxSize { get; set; }
        public bool EnableCache { get; set; }

        public override string ToString()
        {
            string baseString =  base.ToString();
            return String.Format("{0}, MaxSize={1}, EnableCache={2}", baseString,
                                                                      this.MaxSize,
                                                                      this.EnableCache);
        }
    }

    public sealed class Foo5 : FooBase
    {
        internal Foo5() { }
    }
    ////////////////////////////////////////////////
}

Which produces the following output:

Type = ConsoleApplication49.Foo1, Name="Foo 1"
Type = ConsoleApplication49.Foo2, Name="Foo 2", Description="SomeDefaultDescription"
Type = ConsoleApplication49.Foo3, Name="Foo 3", MaxSize=2147483647
Type = ConsoleApplication49.Foo4, Name="Foo 4", MaxSize=99999999, EnableCache=True
Type = ConsoleApplication49.Foo5, Name="Unhandled Foo"
Press any key to continue . . .

I chose to use dynamic here to avoid the following:

  1. using switch/if/else statements e.g. switch(foo.GetType().Name)
  2. explicit type checking statements e.g. foo is Foo1
  3. explicit casting statements e.g. (Foo1)foo

Because of the dynamic conversion, the correct ApplyFooDefaults method gets invoked based on the type of the object passed into HandleFoo(FooBase foo). Any objects that do not have an appropriate ApplyFooDefaults handler method, fall into the "catch all" method, ApplyFooDefaults(FooBase unhandledFoo).

One key part here is that FooBase and the derived classes represent types that are outside of our control and cannot be derived from to add additional interfaces.

Is this a "good" use for dynamic or can this problem be solved in an OOP way without adding additional complexity given the constraints and the fact that this is just to set default property values on these objects?

*UPDATED*

After Bob Horn's answer, I realized that my scenario wasn't complete. Additional constraints:

  1. You can't create the Foos directly, you have to use the FooFactory.
  2. You can't assume the Foo type because the Foo type is specified in configuration and created reflectively.

.

Jim
  • 4,910
  • 4
  • 32
  • 50
  • 1
    @Jon Skeet - I heard you speak at Code Mash 2012 in Sandusky, OH. For the most part it seemed like you didn't care for the use of Dynamic types, in any situation. I'd love to get your thoughts on this question. – Randy Minder May 17 '12 at 19:42
  • 1
    I think your code is very, *very* good as is. – Alex May 18 '12 at 14:19
  • 1
    I think you may want to consider using generics if you are set on using dynamic. Similar: http://stackoverflow.com/q/10132760/1026459. Note: Jon Skeet provides the answer in this link. – Travis J May 18 '12 at 18:32
  • @TravisJ - I liked that solution from the link! In fact I am doing something with reflection that will be nicely replaced by that technique. Unfortunately, adding generics in my scenario here doesn't buy my anything because it is not the type I'm necessarily after but the correct method to actually use the parameter of the correct type to do specific initialization of a concrete object instance. Using generics implies a common algorithm which is actually the opposite here. The "defaulting" diverges based on the type and therefore wouldn't benefit from generics. But I like the technique! – Jim May 18 '12 at 23:03
  • @Jim - Sorry it wasn't of more help, just trying to throw ideas out there :) – Travis J May 18 '12 at 23:09

2 Answers2

1

Well, individual object initialization should happen in the constructors of the types. If the factory fails to do that and outputs an object with a base type only then clearly it is beyond OOP patterns to initialize the objects based on type.

Alas, runtime type detection is the way to go and dynamic just does that, so yes, your solution is very pretty. (but the third party lib is not, because it forces you to use dynamic types)

M.Stramm
  • 1,289
  • 15
  • 29
  • @M. Stramm, I think this is a problem inherent to reflectively created types based on configuration. Where the code doesn't know the concrete type that is going to be created until run-time. Now if you own the code, certainly you can set your defaults in the constructor. But for classes from third party vendors, unless they give you specific hooks, it seems like a run-time dynamic inspection of the type, using dynamics or otherwise, is your only avenue. – Jim May 18 '12 at 19:16
  • @Jim Agreed. Basically it is the factory's responsibility to do object initialization and the calling code should never worry about the type of the created objects. If the factory fails to do this you are *forced* to break the pattern and use run-time type checking. So what I was trying to say is there is no nicer way around this because the third party factory doesn't play fair. – M.Stramm May 18 '12 at 21:27
  • I'm going to accept your answer in the absence of any others chiming in with any alternative OOP approach. – Jim Jun 15 '12 at 13:21
0

A possible way to do this and avoid dynamic would be to make the ApplyFooDefaults() extension methods:

public static class FooExtensions
{
    public static void ApplyFooDefaults(this Foo1 foo1)
    {
        foo1.Name = "Foo 1";

        Console.WriteLine(foo1);
    }

    public static void ApplyFooDefaults(this Foo2 foo2)
    {
        foo2.Name = "Foo 2";
        foo2.Description = "SomeDefaultDescription";

        Console.WriteLine(foo2);
    }

    public static void ApplyFooDefaults(this Foo3 foo3)
    {
        foo3.Name = "Foo 3";
        foo3.MaxSize = Int32.MaxValue;

        Console.WriteLine(foo3);
    }

    public static void ApplyFooDefaults(this Foo4 foo4)
    {
        foo4.Name = "Foo 4";
        foo4.MaxSize = 99999999;
        foo4.EnableCache = true;

        Console.WriteLine(foo4);
    }

    public static void ApplyFooDefaults(this FooBase unhandledFoo)
    {
        unhandledFoo.Name = "Unhandled Foo";
        Console.WriteLine(unhandledFoo);
    }
}

At some point in your program, you have to create each foo. When you do, call the extension methods then:

static void Main(string[] args)
{
    List<FooBase> foos = new List<FooBase>();

    Foo1 foo1 = new Foo1();
    foo1.ApplyFooDefaults();
    foos.Add(foo1);

    Foo2 foo2 = new Foo2();
    foo2.ApplyFooDefaults();
    foos.Add(foo2);

    Foo3 foo3 = new Foo3();
    foo3.ApplyFooDefaults();
    foos.Add(foo3);

    Foo4 foo4 = new Foo4();
    foo4.ApplyFooDefaults();
    foos.Add(foo4);

    Foo5 foo5 = new Foo5();
    foo5.ApplyFooDefaults();
    foos.Add(foo5);

    Console.WriteLine("Press any key to end.");
    Console.ReadKey();
}

enter image description here

Bob Horn
  • 33,387
  • 34
  • 113
  • 219
  • I forgot one aspect of the problem, the Foos are created via reflection also out of our control (from configuration). I will update the example to reflect that. – Jim May 17 '12 at 20:41
  • Also, you don't know which Foos are created because the type is specified in configuration, hypothetically. – Jim May 17 '12 at 20:52
  • LOL. You're just going to keep adding restrictions until the only option left is dynamic, aren't you? :) – Bob Horn May 17 '12 at 22:25
  • -no, this is the scenario. You do not know the concrete class until runtime because objects are specified in configuration. If I knew the concrete class I would not be posting this question here. :-) but I am interested in any simple elegant solutions that don't use dynamics. – Jim May 17 '12 at 22:46