Skip to content
13 changes: 11 additions & 2 deletions src/Microsoft.VisualStudio.Composition/ExportProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,17 @@ private protected ExportInfo CreateExport(ImportDefinition importDefinition, IRe
memberValueFactory = () =>
{
Verify.NotDisposed(this);
PartLifecycleTracker maybeSharedValueFactory = this.GetOrCreateValue(originalPartTypeRef, constructedPartTypeRef, partSharingBoundary, importDefinition.Metadata, nonSharedInstanceRequired, nonSharedPartOwner: null);
return (GetValueFromMember(maybeSharedValueFactory.GetValueReadyToRetrieveExportingMembers(), exportingMemberRef.MemberInfo), nonSharedInstanceRequired ? maybeSharedValueFactory : null);

// For static members, we don't need to instantiate the declaring type
if (exportingMemberRef.IsStatic())
{
return (GetValueFromMember(null, exportingMemberRef.MemberInfo), null);
}
else
{
PartLifecycleTracker maybeSharedValueFactory = this.GetOrCreateValue(originalPartTypeRef, constructedPartTypeRef, partSharingBoundary, importDefinition.Metadata, nonSharedInstanceRequired, nonSharedPartOwner: null);
return (GetValueFromMember(maybeSharedValueFactory.GetValueReadyToRetrieveExportingMembers(), exportingMemberRef.MemberInfo), nonSharedInstanceRequired ? maybeSharedValueFactory : null);
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,31 @@ private object CreateExportFactory(RuntimePartLifecycleTracker importingPartTrac
return exportProvider;
}

// For static member exports, we don't need to create a part lifecycle tracker
if (export.MemberRef != null && export.Member!.IsStatic())
{
partLifecycle = null;
return lazy ? ConstructLazyStaticExportedValue(export) :
ConstructStaticExportedValue(export);
}

var constructedType = GetPartConstructedTypeRef(exportingRuntimePart, import.Metadata);

partLifecycle = this.GetOrCreateValue(import, exportingRuntimePart, exportingRuntimePart.TypeRef, constructedType, importingPartTracker);

return lazy ? ConstructLazyExportedValue(import, export, importingPartTracker, partLifecycle, this.faultCallback) :
ConstructExportedValue(import, export, importingPartTracker, partLifecycle, this.faultCallback);

static Func<object?> ConstructLazyStaticExportedValue(RuntimeComposition.RuntimeExport export)
{
return () => ConstructStaticExportedValue(export);
}

static object? ConstructStaticExportedValue(RuntimeComposition.RuntimeExport export)
{
return GetValueFromMember(null, export.Member!, null, export.ExportedValueTypeRef.Resolve());
}

static Func<object?> ConstructLazyExportedValue(RuntimeComposition.RuntimeImport import, RuntimeComposition.RuntimeExport export, RuntimePartLifecycleTracker? importingPartTracker, PartLifecycleTracker partLifecycle, ReportFaultCallback? faultCallback)
{
// Avoid inlining this method into its parent to avoid non-lazy path from paying for capture allocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Microsoft.VisualStudio.Composition.Tests
using Xunit;
using CompositionFailedException = Microsoft.VisualStudio.Composition.CompositionFailedException;
using MefV1 = System.ComponentModel.Composition;
using MefV2 = System.Composition;

[Trait("Static", "")]
public class StaticMemberExportsTests
Expand Down Expand Up @@ -194,5 +195,104 @@ public class ImportManyWithMetadataPart
[MefV1.ImportMany("Property")]
public List<Lazy<string, IDictionary<string, object>>> ImportingMember { get; set; } = null!;
}

// Test class to verify static members don't cause instantiation
public class ClassWithStaticMemberExports
{
private static bool constructorCalled = false;

public static bool ConstructorCalled
{
get { return constructorCalled; }
set { constructorCalled = value; }
}

public ClassWithStaticMemberExports()
{
constructorCalled = true;
}

[MefV1.Export("StaticField")]
[MefV2.Export("StaticField")]
public static string StaticField = "StaticFieldValue";

[MefV1.Export("StaticProperty")]
[MefV2.Export("StaticProperty")]
public static string StaticProperty => "StaticPropertyValue";

[MefV1.Export("StaticMethod")]
[MefV2.Export("StaticMethod")]
public static string StaticMethod() => "StaticMethodValue";
}

// Test class with mixed static and instance exports
public class ClassWithMixedExports
{
private static bool constructorCalled = false;

public static bool ConstructorCalled
{
get { return constructorCalled; }
set { constructorCalled = value; }
}

public ClassWithMixedExports()
{
constructorCalled = true;
}

[MefV1.Export("StaticMixed")]
[MefV2.Export("StaticMixed")]
public static string StaticExport = "StaticValue";

[MefV1.Export("InstanceMixed")]
[MefV2.Export("InstanceMixed")]
public string InstanceExport => "InstanceValue";
}

[MefFact(CompositionEngines.V1Compat | CompositionEngines.V2Compat | CompositionEngines.V3EmulatingV1 | CompositionEngines.V3EmulatingV2, typeof(ClassWithStaticMemberExports))]
public void StaticFieldExportDoesNotInstantiateClass(IContainer container)
{
ClassWithStaticMemberExports.ConstructorCalled = false;
var value = container.GetExportedValue<string>("StaticField");
Assert.Equal("StaticFieldValue", value);
Assert.False(ClassWithStaticMemberExports.ConstructorCalled, "Constructor should not be called for static field export");
}

[MefFact(CompositionEngines.V1Compat | CompositionEngines.V2Compat | CompositionEngines.V3EmulatingV1 | CompositionEngines.V3EmulatingV2, typeof(ClassWithStaticMemberExports))]
public void StaticPropertyExportDoesNotInstantiateClass(IContainer container)
{
ClassWithStaticMemberExports.ConstructorCalled = false;
var value = container.GetExportedValue<string>("StaticProperty");
Assert.Equal("StaticPropertyValue", value);
Assert.False(ClassWithStaticMemberExports.ConstructorCalled, "Constructor should not be called for static property export");
}

[MefFact(CompositionEngines.V1Compat | CompositionEngines.V2Compat | CompositionEngines.V3EmulatingV1 | CompositionEngines.V3EmulatingV2, typeof(ClassWithStaticMemberExports))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

V1Compat includes V3EmulatingV1, and V2Compat includes V3EmulatingV2.
Please drop the redundant flags.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed redundant flags V3EmulatingV1 and V3EmulatingV2 from test attributes. Tests now use only V1Compat and V2Compat as requested (commit 4648648).

public void StaticMethodExportDoesNotInstantiateClass(IContainer container)
{
ClassWithStaticMemberExports.ConstructorCalled = false;
var value = container.GetExportedValue<Func<string>>("StaticMethod");
Assert.Equal("StaticMethodValue", value());
Assert.False(ClassWithStaticMemberExports.ConstructorCalled, "Constructor should not be called for static method export");
}

[MefFact(CompositionEngines.V1Compat | CompositionEngines.V2Compat | CompositionEngines.V3EmulatingV1 | CompositionEngines.V3EmulatingV2, typeof(ClassWithMixedExports))]
public void StaticExportInMixedClassDoesNotInstantiateClass(IContainer container)
{
ClassWithMixedExports.ConstructorCalled = false;
var value = container.GetExportedValue<string>("StaticMixed");
Assert.Equal("StaticValue", value);
Assert.False(ClassWithMixedExports.ConstructorCalled, "Constructor should not be called when accessing only static export");
}

[MefFact(CompositionEngines.V1Compat | CompositionEngines.V2Compat | CompositionEngines.V3EmulatingV1 | CompositionEngines.V3EmulatingV2, typeof(ClassWithMixedExports))]
public void InstanceExportInMixedClassDoesInstantiateClass(IContainer container)
{
ClassWithMixedExports.ConstructorCalled = false;
var value = container.GetExportedValue<string>("InstanceMixed");
Assert.Equal("InstanceValue", value);
Assert.True(ClassWithMixedExports.ConstructorCalled, "Constructor should be called when accessing instance export");
}
}
}