In the host of MEF, when we declare the imported object through Import, the object will be created when we assemble (Compose). For example:
interface ILogger { void Log(string message); } [Export(typeof(ILogger))] class ConsoleLogger : ILogger { public void Log(string message) { ("logger 1" + message); } } class Host { [Import] ILogger _logger = null; public Host() { var catalog = new AssemblyCatalog(().Assembly); var container = new CompositionContainer(catalog); //The ConsoleLogger object will be created here (this); _logger.Log("hello world"); } }
Sometimes, some components are expensive to create, but they are not used immediately. At this point, we hope to passDelay initializationThe way to delay it until it is used to create, thereby improving performance (often increasing startup speed). MEF supports this mode, we only need to modify the imported declaration form.
[Import] Lazy<ILogger> _logger = null;
This way, the Logger will be delayed until the first time it is used.
MetaData
Sometimes, for the same service, we need to choose one of them to use. MEF provides ImportMany to address this requirement.
Sometimes, for the same service, we need to choose one of them to use. MEF provides ImportMany to address this requirement.
[Export(typeof(ILogger))] class ConsoleLogger : ILogger { public void Log(string message) { (message); } } [Export(typeof(ILogger))] class DbLogger : ILogger { public void Log(string message) { (message); } } class Host { [ImportMany] ILogger[] _logger = null; public Host() { var catalog = new AssemblyCatalog(().Assembly); var container = new CompositionContainer(catalog); (this); _logger.FirstOrDefault(i => i is DbLogger).Log("hello world"); } }
At this time, if we want to use delayed import, it will become the following form:
class Host { [ImportMany] Lazy<ILogger>[] _loggerServices = null; public Host() { var catalog = new AssemblyCatalog(().Assembly); var container = new CompositionContainer(catalog); //The ConsoleLogger object will be created here (this); _loggerServices.FirstOrDefault(i => is DbLogger).("hello world"); } }
At first glance, there is no problem. All loggers are created lately. But after careful analysis, you will find that when you want to find DbLogger, you must traverse all _loggerServices, which will lead to the creation of a Logger. That is, all loggers may be created when using the first logger.
So, how do we implement the logger we only create the required ones? At this time, it is the turn of metadata to appear. The metadata in MEF can attach a data to the Export service object and export it together, so that the corresponding service can be found through element data. First, let's take a look at the final effect:
public interface ILoggerData { string Name { get; } } class Host { [ImportMany] Lazy<ILogger, ILoggerData>[] _logger = null; public Host() { var catalog = new AssemblyCatalog(().Assembly); var container = new CompositionContainer(catalog); (this); _logger.FirstOrDefault(i => == "DB Logger").("hello world"); } }
Here, first declares an interface of metadata type ILoggerData, and then the imported object becomes Lazy<ILogger, ILoggerData>. This object has a property called Metadata, and its type is the ILoggerData declared just now. Exported ILogger objects are created delayed, while metadata is not created delayed. We can find the required Logger object by traversing ILoggerData, so as to create only the Logger object used.
The question now is: how to declare relevant metadata when exporting. MEF provides two ways:
Tagged by ExportMetadataAttribute
This method is to mark the element through the ExportMetaDataAttribute attribute when exporting the service:
[ExportMetadata("Name", "Console Logger")] [Export(typeof(ILogger))] class ConsoleLogger : ILogger { public void Log(string message) { (message); } } [ExportMetadata("Name", "DB Logger")] [Export(typeof(ILogger))] class DbLogger : ILogger { public void Log(string message) { (message); } }
ExportMetaDataAttribute has two parameters, Name and Value, Name is the attribute name and Value is the attribute value. When Compse, MEF will first create a metadata object that implements it, and then assign the value to the attribute name corresponding to Name according to the Value value.
Although it is relatively simple to do this, it has two disadvantages:
- 1. The attribute name is not a strong type
Here we have to strings to give flag attribute names, and there is no syntax-level consistency check between them. In the absence of the nameof operator, it is very easy to make errors once the metadata attribute name changes.
- 2. If the metadata has multiple values, assignment seems to be very burdensome.
If we add a Priority property,
public interface ILoggerData { string Name { get; } int Priority { get; } }
At this time, you must add an ExportMetadata tag to all export objects and write it into the following form:
[ExportMetadata("Name", "DB Logger")] [ExportMetadata("Priority", 3)]
If there are more attributes, I believe programmers will scold you. And once a certain Export object is missed, the modified object will not be imported. This is a runtime error and is very difficult to troubleshoot.
Tags by Attribute
This way, you can tag metadata with an Attribute:
[MetadataAttribute] [AttributeUsage(, Inherited = false, AllowMultiple = false)] class LoggerDataAttribute : Attribute, ILoggerData { public string Name { get; private set; } public LoggerDataAttribute(string name) { = name; } } [LoggerData("Console Logger")] [Export(typeof(ILogger))] class ConsoleLogger : ILogger, ILoggerData { public string Name { get; set; } public void Log(string message) { (message); } } [LoggerData("DB Logger")] [Export(typeof(ILogger))] class DbLogger : ILogger, ILoggerData { public string Name { get; set; } public void Log(string message) { (message); } }
First, declare a LoggerDataAttribute, which must be marked by the MetadataAttribute. Then, add the LoggerDataAttribute to the Export object, so that metadata will be created based on the LoggerDataAttribute when MEF is imported.
It is worth mentioning that the LoggerDataAttribute here does not need to implement the ILoggerData interface itself. It is a DuckType convention and only needs to implement the properties of the metadata. I implement this interface here mainly to enable the compiler to ensure that all metadata attributes are accurately implemented.
This is all about this article about C# implementing lazy loading components in the MEF framework. I hope it will be helpful to everyone's learning and I hope everyone will support me more.