views:

85

answers:

2

My guess until now was that a dynamic type just "switches off" type checking during compilation and does something similar to a type cast when a message is invoked on a dynamic instance. Obviously something else is going on.

The attached NUnit test case shows my problem: Using a dynamic type I can use a method only available in the concrete subclass, but I cannot do the same using a cast (results in InvalidCastException). I'd rather do the casting since this gives me full code completion in VS.

Can anyone explain what's happening and/or give me a hint how I could get code completion in my case without having to reimplement the WorkWithAndCreate-method in every concrete subclass?

cheers, Johannes

using System;
using NUnit.Framework;

namespace SlidesCode.TestDataBuilder
{
    [TestFixture]
    public class MyTest
    {
        [Test]
        public void DynamicWorks()
        {
            string aString = CreateDynamic(obj => obj.OnlyInConcreteClass());
            Assert.AreEqual("a string", aString);
        }

        private static string CreateDynamic(Action<dynamic> action)
        {
            return new MyConcreteClass().WorkWithAndCreate(action);
        }

        [Test]
        public void CastingDoesNotWorkButThrowsInvalidCastException()
        {
            string aString = CreateWithCast(obj => obj.OnlyInConcreteClass());
            Assert.AreEqual("a string", aString);
        }

        private static string CreateWithCast(Action<MyConcreteClass> action)
        {
            return new MyConcreteClass().WorkWithAndCreate((Action<MyGenericClass<string>>) action);
        }
    }

    internal abstract class MyGenericClass<T>
    {
        public abstract T Create();
        public T WorkWithAndCreate(Action<MyGenericClass<T>> action)
        {
            action(this);
            return this.Create();
        }
    }

    internal class MyConcreteClass : MyGenericClass<string>
    {
        public override string Create()
        {
            return "a string";
        }

        public void OnlyInConcreteClass()
        {
        }
    }
}

Here's the formatted real world example from my comment:

Customer customer = ACustomer(cust =>
        {
            cust.With(new Id(54321));
            cust.With(AnAddress(addr => addr.WithZipCode(22222)));
        });

private static Address AnAddress(Action<AddressBuilder> buildingAction)
{
    return new AddressBuilder().BuildFrom(buildingAction);
}

private static Customer ACustomer(Action<CustomerBuilder> buildingAction)
{
    return new CustomerBuilder().BuildFrom(buildingAction);
}

Some details are missing from it but I hope it makes the purpose clear.

+1  A: 

This is an example of how to use dynamic:

http://msdn.microsoft.com/en-us/library/dd264736.aspx

You said:

My guess until now was that a dynamic type just "switches off" type checking during compilation and does something similar to a type cast when a message is invoked on a dynamic instance

Actually, it uses reflections to look up the methods, properties, and fields you invoke by name, at runtime. No casting is done, unless you actually cast the object back to its underlying type.

As for your actual problem, can you give a more concrete example? There may be a better design, but you haven't told us what you're trying to do - just what you're currently doing.

Hazarding a guess, you may want to use a base interface, and make all your functions accept that base interface. Then put the methods you want to call on that interface, and implement them in your concrete types. Usually dynamic is used as a work around when you don't have a base type, or can't modify the base type to add virtual or abstract methods.

If you really want to get this working, as-is, you must write it with a generic type parameter, not a dynamic type parameter. See Pieter's solution for how to do this properly.

Merlyn Morgan-Graham
What I'm trying to do is to come up with an easy to syntax for building test data in test cases. The standard pattern (from the Java community) is to have a fluent interface on the builder objects and call Build() as the last step to actually create the object. Using a lambda instead gets rid of the Build() but introduces other challenges - especially if I want to create a generic way to do it.
johanneslink
@johanneslink: Can you provide some sample syntax you want to enable, with a couple different scenarios? It's easier to work backwards that way. "building test data" can mean any number of things. Making your requirements too generic too early may be some of the trouble, here.
Merlyn Morgan-Graham
@johanneslink: I find it is easier to duplicate patterns and code a few times, then attempt to bridge the concepts to a generic concept after I've got some concrete requirements going. Making a good fluent interface (basically a domain specific language) is complicated enough to start with.
Merlyn Morgan-Graham
Here's a real world example:customer = ACustomer(cust => { cust.With(new Id(54321)); cust.With(AnAddress(addr => addr.WithZipCode(22222))); }); private Address AnAddress(Action<AddressBuilder> buildingAction) { return new AddressBuilder().BuildFrom(buildingAction); } private Customer ACustomer(Action<CustomerBuilder> buildingAction) { return new CustomerBuilder().BuildFrom(buildingAction); }Making BuildFrom generic solves the code completion issue
johanneslink
@johanneslink: You could do a couple things here to simplify this code. One would be to simply use initialization syntax: `var customer = new Customer() { Id = new Id(54321), Address = new Address() { ZipCode = 22222 }, };`. If this isn't available, you can do something like what Rhino.Mocks does, and create generic extension methods: `public static T With<T>(this T instance, Action<T> mutator) { mutator(instance); return instance; }`, and use it like: `var customer = new Customer().With(AnId(12345))`. Your `AnId` function would return a delegate - `return c => c.Id = new Id(value)`
Merlyn Morgan-Graham
@johanneslink: Since you'd be making those specific private functions anyhow, might as well make specific private functions as extension methods: `public static Customer With(this Customer instance, Action<T> mutator) { /... }` or even `public static Customer WithId(this Customer instance, int id);`. When you start adding those to all the classes under test, they can get combined in pretty cool ways: `new Customer().WithAddress(Address.Generated().WithZip(54321))`. Of course, if you can use initialization syntax, you don't need a lot of this, unless it does fancy stuff (not just setters).
Merlyn Morgan-Graham
+2  A: 

The reason dynamic works is that dynamic does not depend on compile time knowledge of the object types. MyGenericClass<string> does not have the method OnlyInConcreteClass(), but the instance you are passing of course does have the method and dynamic finds this.

By the way, you can make WorkWithAndCreate work like this:

public T WorkWithAndCreate<T1>(Action<T1> action)
    where T1 : MyGenericClass<T>
{
    action((T1)this);
    return this.Create();
}

Then, the call will work too:

private static string CreateWithCast(Action<MyConcreteClass> action)
{
    return new MyConcreteClass().WorkWithAndCreate(action);
}

You now don't have to cast it anymore.

And concerning your builder, would the following work?

private static TResult AnInstance<TBuilder, TResult>(Action<TBuilder> buildingAction)
    where TBuilder : Builder<TResult>, new()
{
    return new TBuilder().BuildFrom(buildingAction);
}
Pieter
+1. The only way to get the code completion is to have a concrete type, and the only way to make it so your concrete type can change in derived types is to write this as a generic function, with a base-type constraint.
Merlyn Morgan-Graham
That solved the issue at hand- thanks a lot. I'm still open for suggestions of how the builder syntax could be improved but that's probably worth a new question.
johanneslink