Can I use Ninject ConstructorArguments with strong naming?
Asked Answered
S

3

8

Well, I don't know if "strong naming" is the right term, but what I want to do is as follows.

Currently I use ConstructorArgument like e.g. this:

public class Ninja
{
    private readonly IWeapon _weapon;
    private readonly string _name;

    public Ninja(string name, IWeapon weapon)
    {
        _weapon = weapon;
        _name = name;
    }
    // ..more code..
}

public void SomeFunction()
{
    var kernel = new StandardKernel();
    kernel.Bind<IWeapon>().To<Sword>();
    var ninja = kernel.Get<Ninja>(new ConstructorArgument("name", "Lee"));
}

Now, if I rename the parameter "name" (e.g. using ReSharper) the ConstructorArgument won't update, and I will get a runtime error when creating the Ninja. To fix this I need to manually find all places I specify this parameter through a ConstructorArgument and update it. No good, and I'm doomed to fail at some point even though I have good test coverage. Renaming should be a cheap operation.

Is there any way I can make a reference to the parameter instead - such that it is updated when I rename the parameter?

Slur answered 14/4, 2010 at 11:21 Comment(0)
B
5

If you can share more of what you're really trying to achieve, you'll get a better answer. In general, you dont want to be relying on passing a ConstructorArgument at all if that can be helped - it should be a last resort way of shoehorning a parameter value into the creation of a component you don't own and hence can rely on not being renamed [as] willy nilly during refactoring activities. So for normal code, if you can try to keep it to interfaces to make things unambiguous and not be relying on the names that's better.

Cant dig up an example right now but there's a pretty common idiom called static reflection. A supplied ConstructorArgument can match any parameter of that name across any of the constructors so static reflection isnt the most appropriate thing in this instance.

Hence the best that'll static reflection will probably allow you to achieve is something like:

var ninja = ninject.Get<Ninja>( ParamNamesOf(()=>new Ninja( "dummy", "dummy" )).First() );

The typical example you'll see is where one wants to extract the name of a property being accessed on an instance. This is a little different as it needs to work on a constructor invocation expression.

As for finding an appropriate lib that already has just that, exercise for the searcher :D (But I'd suggest finding a better way to express what you want to do that doesnt use ConstructorArgument in preference to this approach anyway.)

Bifocal answered 14/4, 2010 at 12:30 Comment(5)
I'm using this only with components that I own, so they won't suddenly change unless I tell them to. But I don't want to have my hands bound when refactoring...Slur
I understand that one definitely wouldnt want your code to be brittle if you had to sue ConstructorArgument (hence me +1ing the question). I was making the point that if you own the code you can probably do better than leaning on ConstructorArgument in the first place [whereas if you didnt own the code there are more legitimate reasons to need to do this but the negative impacts would be mitigated to a degree by the fact that it's less likely to change].Bifocal
Dont worry - I understand the question and am not trying to give you another non-answer that doesnt solve the problem you could have thought up yourself. (I wouldnt have bothered answering were it not for the fact that I felt you didnt have an answer that listened to your question yet.)Bifocal
No worries - your answer is helpful :-) I guess there is no simple magic bullet in solving this. Will look into static reflection to see if it can help me!Slur
Static Reflection might be helpful, and I already do this in my ViewModel classes to refer RaisePropertyChanged to properties instead of naming them by string. However, it doesn't seem as applicable to the constructor parameters - like you already said.. We'll see if I can make anything out of it..Slur
G
4

As you already noticed, this approach is very fragile and should be avoided. Try a design were you don't need to add the name as a constructor argument. You could do this for instance:

public class Ninja
{
    private readonly IWeapon _weapon;

    public Ninja(IWeapon weapon)
    {
        _weapon = weapon;
    }

    public string Name { get; set; }

    // ..more code..
}

public void SomeFunction()
{
    var kernel = new StandardKernel();
    kernel.Bind<IWeapon>().To<Sword>();
    var ninja = ninject.Get<Ninja>();
    ninja.Name = "Lee";
}

I hope this helps.

Guyot answered 14/4, 2010 at 12:3 Comment(5)
Thx for the comment, but I dunno.. Property injection can be fragile too. If the Ninja can't be used without a Name you need to trust everyone to remember adding a Name after creating the Ninja.Slur
Not necessarily. When not adding the name is invalid, the Ninja class should check this when used (and throw an InvalidOperationException otherwise). Don't forget that with your initial solution (in your question) you have that same problem. You can also create a factory method that will take care of this (the factory will call your container). This way creating and initializing will only be done at one place.Guyot
Sure - I have a problem in my initial solution, but not the same problem. Factory does sound like a good idea. Will have a look to see if it can help in my situation!Slur
Steven, I totally agree with you. Although that you provide a constructor with parameters required, it doesn't prevent user of that class to pass empty parameters like String.Empty as a user name and this is the same thing that forgot to set the name property in the case of class with no parameters in the constructors. The advantage of using parameters in constructors is to better inform the user of what parameters is required but again, doesn't prevent user to pass null parameters. This is why if you check in the framework of microsoft with reflector, there is always check for parameters.Cavour
In the past, I loved set parameters in constructor but except the advantage of better inform the user on how to use the class, you have much advantages to use a better architecture that check every single required parameters, have dependency injection activated with tool like Ninject than stick with parameters in constructor.Cavour
A
0

Yes you can.

I have imlemented this:

    public string GiveConstuctorArgumentName(Type class, Type constructorArgument)
    {
       var cons = class.GetConstructors();

       foreach (var constructorInfo in cons)
       {
          foreach (var consParameter in constructorInfo.GetParameters())
          {
             if (consParameter.ParameterType == constructorArgument)
             {
                return consParameter.Name;
             }
          }
       }

       throw new InstanceNotFoundException();
    }

Its without LINQ, but its a good start point to understand how its work.

Admiralty answered 20/2, 2013 at 12:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.