Why use ImportingConstructor attribute?
Asked Answered
C

4

17

I'm trying to understand when [ImportingConstructor] would be more appropriate than decorating properties with [import]. Is it a personal preference, or something that allows classes to be constructed by other DI containers or are there benfits over [import]?

I'd think that maybe if you didn't want to expose public properties but MEF will resolve private fields too, so again, where is the benefit?

Cremator answered 31/5, 2012 at 20:9 Comment(0)
C
26

The problem with using [Import] is that it separates the creation of an object into two distinct and observable phases: created and initialized. Where [ImportingConstructor] allows this to remain as a single phase exactly as it would for every other .Net object.

This difference becomes observable in a number of ways

  1. Adding a new [Import] on a field changes the logical contract of a type. Yet it doesn't change the public or usage contract. This means any code which previously compiled will continue to compile even though the objects dependencies have changed (think unit tests). This takes what should be a compile time error and makes it a runtime one.
  2. Code Contracts are unusable if you have an [Import]. The contract verification engine properly recognizes that all fields can exist as null values and will require a check before every use of a field.
  3. Even though your object can logically have fields which are set at initialization time and never reset afterwards, you can't express this with readonly as you would with a normal C# object.
Churchman answered 31/5, 2012 at 20:17 Comment(2)
+1 #1 seems so obvious now. I was unaware of the code contract issue. Can you explain #3 a little more?Cremator
@Cremator with respect to #3, in my experience most of the time you do an [Import] it's for a field which once set will never change. In C# this is expressed by making the field readonly but that doesn't work in the [Import] case because the field is assigned after the constructor finishes which is a violation of readonly. This actually (and scarily) will work via reflection but makes its use with non-MEF composition virtually impossibleChurchman
U
12

Instead of thinking purely in terms of MEF, look at your class design in a broader sense. Typically when you are designing a class, you have a set of associated properties, these could be services, e.g.,

public class MyService
{
  public ILogger Logger { get; set; }

  public void SaySomething()
  {
    Logger.Log("Something");
  }
}

Now, I could go ahead and create an instance of that:

var service = new MyService();

And now, if I try and use the method:

service.SaySomething();

If I don't know explicitly that I have to also initialise my Logger property:

var service = new MyService() { Logger = new ConsoleLogger() };

(or):

var service = new MyService();
service.Logger = new ConsoleLogger();

Then the error won't become apparent until runtime. If we were to redefine the class:

public class MyService
{
  private readonly ILogger _logger;

  public MyService(ILogger logger)
  {
    if (logger == null) throw new ArgumentNullException("logger");

    _logger = logger;
  }

  public void SaySomething()
  {
    _logger.Log("Something");
  }
}

Now, if you try and create an instance of MyService, you explicitly have to provide this additional service (ILogger) for the object to be initialised correctly. This helps a number of ways:

  1. You express the dependencies your type requires, and this forms an initialisation contract that must be satisfied to ensure the type is created in a usable state.
  2. You decrease the risk of runtime errors by ensuring services are passed to your type.

You can improve on this design more by using Code Contracts (as mentioned by @JaredPar) to incude static checking at compile time.

In terms of MEF, you can get away with using [Import] instead of [ImportingConstructor] as MEF will throw an exception when it cannot satisfy all imports on a type, and will only return the type after both initialisation ([ImportingConstructor]) and then [Import]s.

Constructor injection is generally preferable.

Unimposing answered 1/6, 2012 at 11:4 Comment(0)
M
5

By using [ImportingConstructor], you allow one class that serves as an export to import its dependencies. This dramatically simplifies the architecture, as you can decouple the dependencies of a concrete object from its implementation.

Normally, you'd use [ImportingConstructor] on a type that is, itself, marked as [Export]. When the type is composed, the constructor arguments will get provided by MEF.

Mm answered 31/5, 2012 at 20:17 Comment(2)
Is that to guarantee that the required imports are available before the export is resolved to an import?Cremator
@Cremator Yes, without this, you'd have to construct the object in an import, have it in an invalid state, then recompose it to fill in the dependencies, then call some form of initialization... That's super ugly.Mm
P
0

Recomposition is also not supported on constructor parameters.

Parous answered 7/11, 2013 at 23:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.