The Export attribute is sealed..

Oct 27, 2008 at 10:08 PM
Edited Oct 28, 2008 at 11:32 AM
Hi,

I would like to be able to subclass the Export attribute. Something along those lines:

class MyExportAttribute : ExportAttribute
{
    public MyExportAttribute(string c)
        : base(c)
    {
             // ...
    }
}

// And to export methods..
[MyExport("Test")]
public void ExportedMethod(int i)
  {
       Console.WriteLine(i);
   }

.. but ExportAttribute is currently sealed. It would be great if you could support the above scenario in the next release (un-seal the attributes).

Thanks,
Sebastien.



Oct 27, 2008 at 10:19 PM
Edited Oct 28, 2008 at 11:33 AM
.
Oct 28, 2008 at 11:20 AM

Un-sealing the class and subclassing ExportAttribute seems to work, at least on a simple example. But modifying the MEF library code is not a satisfactory solution, and more importantly I am not sure if some part of the code rely on the fact that ExportAttribute is sealed.

It's great that you guys make the source code available, thanks for that. It seems that the unit tests / regression tests are not part of the source code; it would make sense to include the tests in the source code repository for those who want to explore MEF under the hood, and experiment with some implementation alternatives. (I am aware that there are typically legal issues attached with releasing test code, but -if- it is possible it would be great.)

Thanks,
Sebastien.








Coordinator
Oct 29, 2008 at 1:04 AM
Can you explain why you would want to derive from the ExportAttribute?  What functionality would you like to add?

Thanks,
Daniel
Oct 29, 2008 at 9:38 AM

Hi Daniel,

One would want to derive the ExportAttribute to simply rename the attribute to a more appropriate name in the context it's used, or to add further state to the attribute. As a concrete example, let's say I want assign a value to what you call "the contract name" using a counter:

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class CountingExportAttribute : ExportAttribute
    {
        public static int Count {get;set;}

        public CountingExportAttribute()
            : base((Count++).ToString())
        {
        }
    }

From a more general point of view, I think it's preferable to keep classes open because there will always be scenarios that you have not anticipated. Even if some properties are not used by the MEF framework they are useful through reflection. (One should not justify why classes are open but why they are closed.)

Sebastien.

Oct 29, 2008 at 2:34 PM
I disagree in this case, I'm afraid! MEF is a complex concept as is. To add more complexity by renaming standards is wrong. If anything you should name the attribute the same in your namespace.

Why would you want to count the number of crations of the attribute?

Cheers,

M.

On Wed, Oct 29, 2008 at 09:38, vaucouleur <notifications@codeplex.com> wrote:

From: vaucouleur


Hi Daniel,

One would want to derive the ExportAttribute to simply rename the attribute to a more appropriate name in the context it's used, or to add further state to the attribute. As a concrete example, let's say I want assign a value to what you call "the contract name" using a counter:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CountingExportAttribute : ExportAttribute
{
public static int Count {get;set;}

public CountingExportAttribute()
: base((Count++).ToString())
{
}
}

From a more general point of view, I think it's preferable to keep classes open because there will always be scenarios that you have not anticipated. Even if some properties are not used by the MEF framework they are useful through reflection. (One should not justify why classes are open but why they are closed.)

Sebastien.

Read the full discussion online.

To add a post to this discussion, reply to this email (MEF@discussions.codeplex.com)

To start a new discussion for this project, email MEF@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com




--
Magnus Mårtensson
Senior Consultant - Scrum Master - MCSD, MCTS
Dotway AB

Tel: +46 (768) 51 00 36

http://blog.noop.se/
Oct 29, 2008 at 11:58 PM
Edited Oct 30, 2008 at 12:11 AM

Hi Magnus,

Counting is useful in general to generate identifiers, but really this was just a made-up example.  I could come up with many other scenarios, such as for example being able to state if the exported code is proved, or just tested, under which license it is exported etc.
[MyExport("MyName", Trust = Quality.Tested, License = "Blah")]

My point was not about that particular example but rather about opening or closing classes in general, and about closing this attribute in particular. You cannot anticipate completely what people will want to do with this framework. The beauty of OO is that classes are open by default which let people extend data types -- and this add little complexity thanks to polymorphism. That being said, there are also good reasons to close a particular class, and it could very well be the case here.  If there is no good reason to seal this class, my suggestion is that it should be open.

Sebastien.

Oct 30, 2008 at 1:50 AM
Hi vaucouleur,

If you want to add state to the export, you can export the 'Trust' and 'License' values as metadata:
 
[Export("MyName")]
[ExportMetadata("Trust", Quality.Tested)]
[ExportMetadata("License", "Blah")]
public class MyExport
{
...
}

You can also require that this metadata be present on the Import side (although this isn't necessary if the metadata is not required) with the ImportRequiredMetadataAttribute:

public class Importer
{
    [Import]
    [ImportRequiredMetadata("Trust")]
    [ImportRequiredMetadata("License")]
    public Export<MyExport> Import { get; set; }  // the Import.Metadata dictionary will contain all imported metadata, required or not
}

Alternatively, you can use strongly-typed metadata views to specify required metadata on the import:
public class Importer
{
    [Import]
    public Export<MyExport, IMyMetadataView> Import { get; set; }
}

public interface IMyMetadataView
{
    public Quality Trust { get; set; }
    public string License { get; set; }
}

As for sealing attributes, it is typically done for two reasons (there may be more, but this is from what I understand):
  • Performance: Sealed attributes are faster to look-up through reflection (http://msdn.microsoft.com/en-us/library/ms229023.aspx). I can't say I know much about this topic, so maybe someone else on the forum has a better explanation for the performance improvement.
  • Framework evolution: Sealed classes can be unsealed in a later version without causing breaking changes between versions. However, an unsealed class has to remain unsealed to avoid breaking changes. Because of this, unless certain scenarios cannot be accomplished through other means, a class (and especially attributes) will typically remained sealed in the .Net framework.
Hope that helps,
Jad
Oct 30, 2008 at 9:56 AM

Thanks for the detailed answer Jad. First I was not sure what you meant by the "Import.Metadata dictionary", so I tried to simply query the metadata on the import side, downcasting to ExportMetadataAttribute, and it seems to work just fine. This will indeed deal with some of the extensibility scenarios. It's unfortunate that the ExportAttribute will stay sealed but I understand that you stick to the guidelines.

Sebastien.

(Here's the code I used for my quick test.)

    [Import("Test")]
    [ImportRequiredMetadata("License")]
    public Action<int> ImportedMethod { get; set; }
   
    [...]

        var m = ImportedMethod.Method;
        foreach (var ca in m.GetCustomAttributes(true))
        {
            var em = ca as ExportMetadataAttribute;
            if (em!=null) {
                Console.WriteLine(em.Name);
                Console.WriteLine(em.Value as string);
            }
        }

Coordinator
Oct 30, 2008 at 9:57 PM

I would not recommend doing it this way.  It works right now but there's no guarantee that in the future the ImportedMethod.Method property would refer to the original method (it might be an internal MEF method that would call your method).  Also it won't work if you are importing an interface or anything besides a method.

As Jad mentioned, you should use Export<T> for this.  This class allows you access to the metadata, and it also delays creation of the exported object until you call GetExportedObject().  Here is a sample of how to use it.

[Import("Test")]

public Export<Action<int>> MethodImport { get; set; }

 

public void Test()

{

    //  Inspect metadata

    foreach(KeyValuePair<string, object> kvp in MethodImport.Metadata)

    {

        Console.WriteLine(kvp.Key);

        Console.WriteLine(kvp.Value);

    }

 

    //  Call imported method

    var method = MethodImport.GetExportedObject();

    method(42);           

}


Thanks,
Daniel

Oct 31, 2008 at 12:09 AM

Yes, I overlooked the Export<T> part in Jad's reply. It's clear now.

Thanks for the sample,
Sebastien.