вторник, 5 октября 2010 г.

Introducing UStatic: check Unity injection statically

A few months ago I’ve made post about wrapper over Unity container that allows to verify object structure during compilation. At that time this wrapper supports only one type of injection – through properties (setter injection). Today it evolve into more evil and aggressive creature that besides property injection can initialize objects through calling specified constructors (constructor injection) and invoking methods.

Basically idea remains the same: we use lambdas for collecting user-defined configuration and describe dependencies as expression trees (amazing stuff, BTW! it can be applied to solve dozens of problems, from describing queries to being source for automatic generation of WPF ViewModels). With properties everything is pretty trivial:

using System;
using Microsoft.Practices.Unity;
using UStatic;

namespace UStaticTest
{
class Program
{
static void Main()
{
var container = new UnityContainer();

container.RegisterType<AnotherTestObject>(
c => c
.SetName("Test1")
.SetValueProperty(_ => _.Name, "The one you need")
);

container.RegisterType<AnotherTestObject>(
c => c
.SetValueProperty(_ => _.Name, "How did you get it??")
);

container.RegisterType<TestObject>(
c => c
.SetResolvedProperty(_ => _.A, "Test1")
.SetValueProperty(_ => _.B, "String value")
);

var testObject = container.Resolve<TestObject>();
Console.WriteLine(testObject.A.Name); // The one you need
Console.WriteLine(testObject.B); // String value
}
}

class TestObject
{
public AnotherTestObject A { get; set; }
public string B { get; set; }
}

public class AnotherTestObject
{
public string Name { get; set; }
}
}

We register AnotherTestObject twice: first time with name and  second – anonymous. For both registrations we specify value being set into property Name. After that we register TestObject with resolved property A. Our extension translates expression trees into various subtypes of InjectionMember but preserving type related information. This is funny but not new, we have already seen it in the previous post. What is really interesting is how can we use expression trees to describe constructor and method call. If we want to pass constant values everything is straightforward – just make another method in the interface, name it SetInitMethod and analyze expression in the similar way with SetResolvedProperty. But was if we want method arguments to be resolved from container during instance initialization? This is usual and widely used scenario. We need some auxiliary types to denote holes in our source expression so later this holes can be filled using values from container.

    /// <summary>
/// This type is used to make typed placeholders in expression trees that will be filled with actual values during resolution.
/// </summary>
public static class Param
{
internal static readonly MethodInfo ResolvedMethod = new Func<ResolvedParameterPlaceholder<int>>(Resolved<int>).Method.GetGenericMethodDefinition();
internal static readonly MethodInfo ResolvedWithNameMethod = new Func<string, ResolvedParameterPlaceholder<int>>(Resolved<int>).Method.GetGenericMethodDefinition();

public sealed class ResolvedParameterPlaceholder<T>
{
private ResolvedParameterPlaceholder() { }

public static implicit operator T(ResolvedParameterPlaceholder<T> p)
{
throw NoDynamicInvokation();
}
}

/// <summary>
/// Denotes placeholder for resolved value with specified type.
/// </summary>
/// <typeparam name="T">Type of target object</typeparam>
/// <returns>Resolution placeholder</returns>
public static ResolvedParameterPlaceholder<T> Resolved<T>()
{
throw NoDynamicInvokation();
}

/// <summary>
/// Denotes placeholder for resolved value with specified type and name.
/// </summary>
/// <typeparam name="T">Type of target object</typeparam>
/// <param name="name">Name of target object</param>
/// <returns>Resolution placeholder</returns>
public static ResolvedParameterPlaceholder<T> Resolved<T>(string name)
{
throw NoDynamicInvokation();
}

private static Exception NoDynamicInvokation()
{
return new InvalidOperationException("This operation is intended to be used in expression trees only");
}
}

ResolvedParameterPlaceholder type will act as placeholder (that’s why he has such weird name). It shouldn’t ever be created, we intended to use it only as marker in expression trees. Implicit conversion operator allows to use this type instead of any other actual types. Our analyzer will process usages of Param.Resolved separately and use ResolvedParameter instead of value. Nice and simple idea and it should also work for constructors. To select constructor we will use new expression + direct values and Param.Resolved.

using System;
using Microsoft.Practices.Unity;
using UStatic;

namespace UStaticTest
{
class Program
{
static void Main()
{
var container = new UnityContainer();

container.RegisterType<AnotherTestObject>(
c => c
.SetValueProperty(_ => _.Name, "AnotherTestObject")
);
container.RegisterType<YetAnotherTestObject>(
c => c
.SetValueProperty(_ => _.Name, "YetAnotherTestObject")
);

container.RegisterType<TestObject>(
c => c
.SetConstructor(() => new TestObject(Param.Resolved<YetAnotherTestObject>()))
.SetInitMethod(_ => _.Initialize(Param.Resolved<AnotherTestObject>()))
);

container.Resolve<TestObject>(); // Initialize: testObject.Name = AnotherTestObject, B = YetAnotherTestObject
}
}

class TestObject
{
public TestObject(YetAnotherTestObject obj)
{
B = obj;
}

public TestObject(YetAnotherTestObject obj, string text)
{
throw new InvalidOperationException("What's happen??");
}

public YetAnotherTestObject B { get; set; }

public void Initialize(AnotherTestObject testObject)
{
Console.WriteLine("Initialize: testObject.Name = {0}, B = {1}", testObject.Name, B.Name);
}
}

public class AnotherTestObject
{
public string Name { get; set; }
}

public class YetAnotherTestObject
{
public string Name { get; set; }
}
}

Complete source code of this project(I’ve named it UStatic) is avaiable here. As always any suggestions, constructive critisicm (and especially huge money donations :) ) are welcomed and appreciated.

Комментариев нет:

Отправить комментарий

 
GeekySpeaky: Submit Your Site!