views:

125

answers:

5

I've written a resolver so I can have a very primitive DI framework. In the framework I allow for a dependency resolver to specify what default types to load if nothing is specified or registered.

However, the way I do the default loading has got me wondering. I'm not sure I'm doing it the best way it could be done.

Example:

T LoadDefaultForType<T>()
{
    T _result = default(T);

    if (typeof(T) == typeof(ISomeThing)
    {
        result = new SomeThing();
    }
    ... more of the same
    else
    {
        throw new NotSupportException("Give me something I can work with!");
    }

    return _result;
}

Update

The use of this would be to get the default object for a given interface in the event that a module or assembly has not configured the interface with a concrete type.

So for instance:

IoC.Resolve<ISomeThing>();

would need to return back to me a SomeThing object if nothing else has been registered to ISomeThing. The LoadDefaultForType in this case is kind of a last ditch effort to use a default (in this case whatever my domain model is).

The Resolve might shed some light on this as well:

T Resolve<T>()
{
    T _result = default(T);
    if (ContainedObjects.ContainsKey(typeof(T))
        _result = (T)ContainedObjects[typeof(T)];
    else
        _result = LoadDefaultForType<T>();
    return _result;
}

Any thoughts? Is there a better way to load default types given that I'm trying to allow for a Convention Over Configuration approach?

+2  A: 
public T LoadDefaultForType<T>()
        where T : new()
    {
        T _result = new T();

        return _result;
    }

the code above would be a better way, but im not sure what it is your're trying todo, more information would help give u a better way of doing whatever it is you're trying to achieve.

I suggest taking a look at Unity for dynamically loading types, ie. Dependency injection

Neil
@Neil Thanks, I'll modify my question to better illustrate. I would definitely use a DI framework if I could, but in this case I have no choice but to roll my own.
Joseph
@Neil Also, I can't use the new constraint because T has to support interfaces, which will be the typical use.
Joseph
where T : IInterface, new()but this means that LoadDefaultType can only support types that derive from IInterface, does this work?
Neil
@Neil Not really, I use LoadDefaultForType like this: LoadDefaultForType<ISomething> and it should return back to me a new Something. Does that make sense? I'm not asking it for a Something, I'm asking it for an ISomething.
Joseph
the new() constraint only works with classes or structs. You cannot instantiate an instance of an interface. This would require that the Resolve() method restrict the type T to being a class or struct.
LBushkin
No wait, i c your problem
Neil
+1  A: 

Neil's approach is the best if T can be resolved (I think it also has to be in the same assembly?).

Within your class, you could create an internal "registry" of sorts that could be used with System.Reflection to instantiate items without the giant switch statement. This preserves your "convention over configuration" while also keeping you DRY.

Edit

Combined with one aspect of LBushkin's answer to show some working code. (At least, it compiles in my head, and is taken from an example that I know works...)

public T LoadDefaultForType<T>()
{
  try
  {
    string interfaceName = typeof(T).AssemblyQualifiedName;

    // Assumes that class has same name as interface, without leading I, and
    // is in ..."Classes" namespace instead of ..."Interfaces"
    string className = interfaceName.Replace("Interfaces.I", "Classes.");

    Type t = Type.GetType(className, true, true);
    System.Reflection.ConstructorInfo info = t.GetConstructor(Type.EmptyTypes);

    return (T)(info.Invoke(null));
  }
  catch
  {
    throw new NotSupportException("Give me something I can work with!");
  }
}

Note that - as written - it won't work across assembly boundaries. It can be done using essentially the same code, however - you just need to supply the assembly-qualified name to the Type.GetType() method. (fixed - use AssemblyQualifiedName instead of FullName; assumes that interface and implementing class are in same assembly.)

GalacticCowboy
If I understand correctly, Neil's solution doesn't fit the bill because it doesn't allow overriding the default type.
Steven Sudit
@GalacticCowboy Neil's approach doesn't really solve my problem. I've updated my question to better explain. What you're describing is exactly what I'm trying to do, but I don't know of a better way to create a "registry" of sorts without doing the giant if else or switch structure. I'm not sure how I would use reflection to solve this problem, either, but I'm open to ideas. An example of something would be really helpful.
Joseph
A: 

One way would be to have a list of AssemblyQualifiedName pairs, with the first containing the base type and the second containing the child to be insantiated. It could be in your App.config or an XML file or whatever else is convenient. Read this list during start-up and use it to populate a Dictionary to use as a look-up table. For the key, you could use the AssemblyQualifiedName of the base, or perhaps the corresponding Type instance. For the value, you should probably consider getting the ConstructorInfo for the child type.

Steven Sudit
I'm with you on that, but that's Configuration, not Convention, and my LoadDefaultForType method is about setting up my Convention. I have other mechanisms in my DI framework to handle Configuration already. The Convention part (this part), is what I'm wondering about.
Joseph
The convention part would be triggered by a TryGet failing. At that point, you could take the AQN and append a standard suffix, such as "Default", and use Type.GetType to look it up, adding its ConstuctorInfo to the table. If this fails, you look up the base type, adding an identity entry to avoid repeating all this work. You'll need to lock the table while doing this, or at least during the few moments you need to access it.
Steven Sudit
A: 

A better way to load the default types is to provide an add method that stores the type in a hash table. This decouples the dependency container from the registration logic.

You may choose later to change the registration code to read the types from some file or something.

Hans Malherbe
I have a Register method in my DI framework as well. Anyone can register an object an associate it with an interface, but that's what I consider the Configuration portion of the framework. What I need is to have a back up plan when someone chooses not to configure the interfaces.
Joseph
+2  A: 

A few of suggestions:

You could create an attribute that can be used to mark the default implementation type of a particular interface. When you attempt to resolve the type, you could search for this attribute on T and use reflection to dynamically instantiate the type.

Alternatively, you could use reflection to search the available (loaded) assemblies or a concrete type that implements the interface. This can be a slow and expensive processes, so it would only make sense if the default case is rare.

Finally, if you're comfortable with naming conventions, you could search for a class that has the same name as the interface but without the leading "I". Not the best approach, but certainly one that can be made to work in a pinch.

LBushkin
I really like your attribute idea. I think that might be the way to go.
Joseph
And with the naming convention approach, that would tie in well to "convention/configuration".
GalacticCowboy