diff --git a/docs/guides/dotnet/cloning.md b/docs/guides/dotnet/cloning.md
index 913073022..37b54fd21 100644
--- a/docs/guides/dotnet/cloning.md
+++ b/docs/guides/dotnet/cloning.md
@@ -103,9 +103,8 @@ cloner.Include(rectangleType);
cloner.Include(vectorType);
```
-`Include` returns the same `MemberCloner` instance. It is therefore also
-possible to create a long method chain of members to include in the
-cloning process.
+`Include` returns the same `MemberCloner` instance, allowing for more fluent
+syntax:
``` csharp
cloner
@@ -255,7 +254,24 @@ var cloner = new MemberCloner(destinationModule, (original, cloned) => {
});
```
-## Injecting the cloned members
+Multiple listeners can also be chained together using the `AddListener` method:
+
+```csharp
+var cloner = new MemberCloner(destinationModule);
+cloner.AddListener(new MyListener1());
+cloner.AddListener(new MyListener2());
+```
+
+This can also be used in the fluent syntax form:
+
+```csharp
+var cloner = new MemberCloner(destinationModule)
+ .AddListener(new MyListener1())
+ .AddListener(new MyListener2());
+```
+
+
+## Injecting cloned members
The `Clone` method returns a `MemberCloneResult`, which contains a
register of all members cloned by the member cloner.
@@ -288,17 +304,37 @@ foreach (var clonedType in clonedTypes)
destinationModule.TopLevelTypes.Add(clonedType);
```
-However, since injecting the cloned top level types is a very common
-use-case for the cloner, AsmResolver defines the
-`InjectTypeClonerListener` class that implements a cloner listener that
-injects all top-level types automatically into the destination module.
-In such a case, the code can be reduced to the following:
+Injecting the cloned top level types is a very common use-case for the cloner.
+AsmResolver defines the `InjectTypeClonerListener` class that implements a
+cloner listener that injects all top-level types automatically into
+the destination module. In such a case, the code can be reduced to the following:
``` csharp
-new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule))
+new MemberCloner(destinationModule)
.Include(rectangleType)
.Include(vectorType)
+ .AddListener(new InjectTypeClonerListener(destinationModule))
.Clone();
// `destinationModule` now contains copies of `rectangleType` and `vectorType`.
```
+
+AsmResolver provides another built-in listener `AssignTokensClonerListener` that
+also allows for preemptively assigning new metadata tokens automatically, should
+this be necessary.
+
+``` csharp
+new MemberCloner(destinationModule)
+ .Include(rectangleType)
+ .Include(vectorType)
+ .AddListener(new InjectTypeClonerListener(destinationModule))
+ .AddListener(new AssignTokensClonerListener(destinationModule))
+ .Clone();
+
+// `destinationModule` now contains copies of `rectangleType` and `vectorType`
+// and all its members have been assigned a metadata token preemptively.
+```
+
+The `AssignTokensClonerListener` uses the target module's `TokenAllocator`. See
+[Metadata Token Allocation](token-allocation.md) for more information on how this
+class operates.
\ No newline at end of file
diff --git a/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs
new file mode 100644
index 000000000..5a1651641
--- /dev/null
+++ b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs
@@ -0,0 +1,33 @@
+namespace AsmResolver.DotNet.Cloning
+{
+ ///
+ /// Provides an implementation of a that preemptively assigns new metadata
+ /// tokens to the cloned metadata members using the target module's .
+ ///
+ public class AssignTokensClonerListener : MemberClonerListener
+ {
+ ///
+ /// Creates a new instance of the token allocator listener.
+ ///
+ /// The module that will contain the cloned members.
+ public AssignTokensClonerListener(ModuleDefinition targetModule)
+ {
+ TargetModule = targetModule;
+ }
+
+ ///
+ /// Gets the module that will contain the cloned members.
+ ///
+ public ModuleDefinition TargetModule
+ {
+ get;
+ }
+
+ ///
+ public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned)
+ {
+ TargetModule.TokenAllocator.AssignNextAvailableToken((MetadataMember) cloned);
+ base.OnClonedMember(original, cloned);
+ }
+ }
+}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs
index 78df400d3..d96da9443 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs
@@ -42,8 +42,8 @@ private void DeepCopyFields(MemberCloneContext context)
{
DeepCopyField(context, field);
var clonedMember = (FieldDefinition)context.ClonedMembers[field];
- _clonerListener.OnClonedMember(field, clonedMember);
- _clonerListener.OnClonedField(field, clonedMember);
+ _listeners.OnClonedMember(field, clonedMember);
+ _listeners.OnClonedField(field, clonedMember);
}
}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs
index af6085a14..205ccf9f5 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs
@@ -57,8 +57,8 @@ private void DeepCopyMethods(MemberCloneContext context)
{
DeepCopyMethod(context, method);
var clonedMember = (MethodDefinition)context.ClonedMembers[method];
- _clonerListener.OnClonedMember(method, clonedMember);
- _clonerListener.OnClonedMethod(method, clonedMember);
+ _listeners.OnClonedMember(method, clonedMember);
+ _listeners.OnClonedMethod(method, clonedMember);
}
}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs
index 7d3e5ea6b..53140cfca 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs
@@ -18,8 +18,8 @@ private void DeepCopyProperties(MemberCloneContext context)
declaringType.Properties.Add(clonedProperty);
}
var clonedMember = clonedProperty;
- _clonerListener.OnClonedMember(property, clonedMember);
- _clonerListener.OnClonedProperty(property, clonedMember);
+ _listeners.OnClonedMember(property, clonedMember);
+ _listeners.OnClonedProperty(property, clonedMember);
}
}
@@ -55,8 +55,8 @@ private void DeepCopyEvents(MemberCloneContext context)
declaringType.Events.Add(clonedEvent);
}
var clonedMember = clonedEvent;
- _clonerListener.OnClonedMember(@event, clonedMember);
- _clonerListener.OnClonedEvent(@event, clonedMember);
+ _listeners.OnClonedMember(@event, clonedMember);
+ _listeners.OnClonedEvent(@event, clonedMember);
}
}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
index 64acfe3a5..cf9862e78 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
@@ -17,7 +17,7 @@ namespace AsmResolver.DotNet.Cloning
///
public partial class MemberCloner
{
- private readonly IMemberClonerListener _clonerListener;
+ private readonly MemberClonerListenerList _listeners;
private readonly Func? _importerFactory;
private readonly ModuleDefinition _targetModule;
@@ -32,7 +32,7 @@ public partial class MemberCloner
///
/// The target module to copy the members into.
public MemberCloner(ModuleDefinition targetModule)
- : this(targetModule, (original, cloned) => { })
+ : this(targetModule, null, null)
{
}
@@ -79,7 +79,9 @@ public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listene
{
_targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule));
_importerFactory = importerFactory;
- _clonerListener = clonerListener ?? CallbackClonerListener.EmptyInstance;
+ _listeners = new MemberClonerListenerList();
+ if (clonerListener is not null)
+ _listeners.Add(clonerListener);
}
///
@@ -262,6 +264,28 @@ public MemberCloner Include(EventDefinition @event)
return this;
}
+ ///
+ /// Adds a member cloner listener to the cloner.
+ ///
+ /// The listener to add.
+ /// The metadata cloner that the listener is added to.
+ public MemberCloner AddListener(Action listener)
+ {
+ _listeners.Add(new CallbackClonerListener(listener));
+ return this;
+ }
+
+ ///
+ /// Adds a member cloner listener to the cloner.
+ ///
+ /// The listener to add.
+ /// The metadata cloner that the listener is added to.
+ public MemberCloner AddListener(IMemberClonerListener listener)
+ {
+ _listeners.Add(listener);
+ return this;
+ }
+
///
/// Clones all included members.
///
@@ -313,8 +337,8 @@ private void DeepCopyTypes(MemberCloneContext context)
{
DeepCopyType(context, type);
var clonedMember = (TypeDefinition)context.ClonedMembers[type];
- _clonerListener.OnClonedMember(type, clonedMember);
- _clonerListener.OnClonedType(type, clonedMember);
+ _listeners.OnClonedMember(type, clonedMember);
+ _listeners.OnClonedType(type, clonedMember);
}
}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs
new file mode 100644
index 000000000..6385b78a1
--- /dev/null
+++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs
@@ -0,0 +1,52 @@
+using System.Collections.ObjectModel;
+
+namespace AsmResolver.DotNet.Cloning
+{
+ ///
+ /// Wraps a list of s into a single instance of .
+ ///
+ public class MemberClonerListenerList : Collection, IMemberClonerListener
+ {
+ ///
+ public void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned)
+ {
+ for (int i = 0; i < Items.Count; i++)
+ Items[i].OnClonedMember(original, cloned);
+ }
+
+ ///
+ public void OnClonedType(TypeDefinition original, TypeDefinition cloned)
+ {
+ for (int i = 0; i < Items.Count; i++)
+ Items[i].OnClonedType(original, cloned);
+ }
+
+ ///
+ public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned)
+ {
+ for (int i = 0; i < Items.Count; i++)
+ Items[i].OnClonedMethod(original, cloned);
+ }
+
+ ///
+ public void OnClonedField(FieldDefinition original, FieldDefinition cloned)
+ {
+ for (int i = 0; i < Items.Count; i++)
+ Items[i].OnClonedField(original, cloned);
+ }
+
+ ///
+ public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned)
+ {
+ for (int i = 0; i < Items.Count; i++)
+ Items[i].OnClonedProperty(original, cloned);
+ }
+
+ ///
+ public void OnClonedEvent(EventDefinition original, EventDefinition cloned)
+ {
+ for (int i = 0; i < Items.Count; i++)
+ Items[i].OnClonedEvent(original, cloned);
+ }
+ }
+}
diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs
index 050c8991a..88b68064c 100644
--- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs
+++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs
@@ -76,7 +76,7 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef
return clonedMethod;
}
- private static FieldDefinition CloneIntializerField(FieldInfo field, out FieldDefinition originalFieldDef)
+ private static FieldDefinition CloneInitializerField(FieldInfo field, out FieldDefinition originalFieldDef)
{
var sourceModule = ModuleDefinition.FromFile(field.Module.Assembly.Location);
originalFieldDef = (FieldDefinition) sourceModule.LookupMember(field.MetadataToken);
@@ -279,10 +279,10 @@ public void CloneConstant()
public void CloneFieldRva()
{
var clonedInitializerField =
- CloneIntializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field);
+ CloneInitializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field);
- var originalData = ((IReadableSegment) field.FieldRva).ToArray();
- var newData = ((IReadableSegment) clonedInitializerField.FieldRva).ToArray();
+ var originalData = ((IReadableSegment) field.FieldRva!).ToArray();
+ var newData = ((IReadableSegment) clonedInitializerField.FieldRva!).ToArray();
Assert.Equal(originalData, newData);
}
@@ -391,5 +391,22 @@ public void CloneAndInject()
Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes));
}
+ [Fact]
+ public void CloneAndInjectAndAssignToken()
+ {
+ var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location);
+ var targetModule = PrepareTempModule();
+
+ var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous));
+
+ var result = new MemberCloner(targetModule)
+ .Include(type)
+ .AddListener(new InjectTypeClonerListener(targetModule))
+ .AddListener(new AssignTokensClonerListener(targetModule))
+ .Clone();
+
+ Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes));
+ Assert.All(result.ClonedMembers, m => Assert.NotEqual(0u, ((IMetadataMember) m).MetadataToken.Rid));
+ }
}
}