Skip to content

Commit c3e5da6

Browse files
committed
[FastClonerShallow]
1 parent f2dced0 commit c3e5da6

File tree

10 files changed

+696
-10
lines changed

10 files changed

+696
-10
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,30 @@ TestPropsWithIgnored original = new TestPropsWithIgnored { A = 42, B = "Test val
8484
TestPropsWithIgnored clone = original.DeepClone(); // clone.B is null (default value of a given type)
8585
```
8686

87+
### Shallow Cloning Members
88+
89+
When you need to copy a reference directly without deep cloning its contents, use `[FastClonerShallow]`.
90+
91+
```csharp
92+
public class TreeNode
93+
{
94+
public string Name { get; set; }
95+
96+
[FastClonerShallow] // <-- reference copied directly
97+
public TreeNode Parent { get; set; }
98+
99+
public List<TreeNode> Children { get; set; }
100+
}
101+
102+
TreeNode child = new TreeNode {
103+
Name = "Child",
104+
Parent = new TreeNode { Name = "Root" }
105+
};
106+
TreeNode clone = child.DeepClone();
107+
```
108+
109+
This differs from `[FastClonerIgnore]` which leaves the member as `null`/default. With `[FastClonerShallow]`, the original reference is preserved.
110+
87111
You might also need to exclude certain types from ever being cloned. To do that, put offending types on a blacklist:
88112
```cs
89113
FastCloner.FastCloner.IgnoreType(typeof(PropertyChangedEventHandler)); // or FastCloner.FastCloner.IgnoreTypes([ .. ])

src/FastCloner.SourceGenerator/ImplicitTypeAnalyzer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ bool TryHandleComponent(ITypeSymbol componentType, out MemberModel? componentMem
103103
IsNullable: false,
104104
HasGetter: true,
105105
HasSetter: true,
106-
SetterIsAccessible: true
106+
SetterIsAccessible: true,
107+
IsShallowClone: false
107108
);
108109
return true;
109110
}

src/FastCloner.SourceGenerator/MemberCloneGenerator.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ public static string GetMemberAssignment(CloneGeneratorContext context, MemberMo
1818
case { IsProperty: true, HasGetter: true, HasSetter: false, IsInitOnly: false }:
1919
return string.Empty;
2020
default:
21+
// Handle shallow clone - direct reference copy (before checking TypeKind)
22+
if (member.IsShallowClone)
23+
{
24+
return $"{memberName} = {sourceVar}.{memberName}";
25+
}
26+
2127
switch (member.TypeKind)
2228
{
2329
case MemberTypeKind.Safe:
@@ -118,6 +124,13 @@ public static void WriteMemberCloning(CloneGeneratorContext context, MemberModel
118124
WriteGetterOnlyCollectionPopulation(context, member, resultVar, sourceVar, stateVar);
119125
return;
120126
default:
127+
// Handle shallow clone - direct reference copy (before checking TypeKind)
128+
if (member.IsShallowClone)
129+
{
130+
sb.AppendLine($" {resultVar}.{memberName} = {sourceVar}.{memberName};");
131+
return;
132+
}
133+
121134
switch (member.TypeKind)
122135
{
123136
case MemberTypeKind.Safe:

src/FastCloner.SourceGenerator/MemberCollector.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ public static List<MemberAnalysis> GetMembers(
4848
{
4949
if (!HasIgnoreAttribute(property, compilation))
5050
{
51-
members.Add(new MemberAnalysis(MemberModel.Create(property, nullabilityEnabled, compilation), property.Type));
51+
bool isShallow = HasShallowAttribute(property, compilation);
52+
members.Add(new MemberAnalysis(MemberModel.Create(property, nullabilityEnabled, compilation, isShallow), property.Type));
5253
}
5354
}
5455
}
@@ -59,7 +60,8 @@ public static List<MemberAnalysis> GetMembers(
5960

6061
if (!HasIgnoreAttribute(field, compilation))
6162
{
62-
members.Add(new MemberAnalysis(MemberModel.Create(field, nullabilityEnabled, compilation), field.Type));
63+
bool isShallow = HasShallowAttribute(field, compilation);
64+
members.Add(new MemberAnalysis(MemberModel.Create(field, nullabilityEnabled, compilation, isShallow), field.Type));
6365
}
6466
}
6567
}
@@ -161,4 +163,21 @@ private static bool HasIgnoreAttribute(ISymbol member, Compilation compilation)
161163

162164
return false;
163165
}
166+
167+
private static bool HasShallowAttribute(ISymbol member, Compilation compilation)
168+
{
169+
INamedTypeSymbol? shallowAttribute = compilation.GetTypeByMetadataName("FastCloner.Code.FastClonerShallowAttribute");
170+
if (shallowAttribute == null)
171+
return false;
172+
173+
foreach (AttributeData? attr in member.GetAttributes())
174+
{
175+
if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, shallowAttribute))
176+
{
177+
return true;
178+
}
179+
}
180+
181+
return false;
182+
}
164183
}

src/FastCloner.SourceGenerator/MemberModel.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ internal readonly record struct MemberModel(
8282
// Property accessor capabilities
8383
bool HasGetter, // Whether the property has a getter
8484
bool HasSetter, // Whether the property has a setter (regular, not init-only)
85-
bool SetterIsAccessible // Whether the setter is publicly accessible (not private/protected)
85+
bool SetterIsAccessible, // Whether the setter is publicly accessible (not private/protected)
86+
bool IsShallowClone // Whether the member should be shallow cloned (has [FastClonerShallow] attribute)
8687
) : IEquatable<MemberModel>
8788
{
88-
public static MemberModel Create(IPropertySymbol property, bool nullabilityEnabled, Compilation compilation)
89+
public static MemberModel Create(IPropertySymbol property, bool nullabilityEnabled, Compilation compilation, bool isShallowClone = false)
8990
{
9091
(MemberTypeKind typeKind, string? elementName, string? keyName, string? valueName, bool elementSafe, bool elementClonable, bool keySafe, bool keyClonable, bool valSafe, bool valClonable, bool requiresFastCloner, CollectionKind collectionKind, string? concreteType, int arrayRank)
9192
= AnalyzeType(property.Type, compilation);
@@ -130,10 +131,11 @@ public static MemberModel Create(IPropertySymbol property, bool nullabilityEnabl
130131
isNullable,
131132
hasGetter,
132133
hasSetter,
133-
setterIsAccessible);
134+
setterIsAccessible,
135+
isShallowClone);
134136
}
135137

136-
public static MemberModel Create(IFieldSymbol field, bool nullabilityEnabled, Compilation compilation)
138+
public static MemberModel Create(IFieldSymbol field, bool nullabilityEnabled, Compilation compilation, bool isShallowClone = false)
137139
{
138140
(MemberTypeKind typeKind, string? elementName, string? keyName, string? valueName, bool elementSafe, bool elementClonable, bool keySafe, bool keyClonable, bool valSafe, bool valClonable, bool requiresFastCloner, CollectionKind collectionKind, string? concreteType, int arrayRank)
139141
= AnalyzeType(field.Type, compilation);
@@ -172,7 +174,8 @@ public static MemberModel Create(IFieldSymbol field, bool nullabilityEnabled, Co
172174
isNullable,
173175
hasGetter,
174176
hasSetter,
175-
setterIsAccessible);
177+
setterIsAccessible,
178+
isShallowClone);
176179
}
177180

178181
private static (MemberTypeKind kind, string? elem, string? key, string? val, bool elemSafe, bool elemClon, bool keySafe, bool keyClon, bool valSafe, bool valClon, bool requiresFastCloner, CollectionKind collKind, string? concreteType, int arrayRank)

src/FastCloner.SourceGenerator/NestedTypeCollector.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ public static void Collect(
8383
false, // IsNullable
8484
true, // HasGetter - helper methods always have access
8585
true, // HasSetter - helper methods always have access
86-
true // SetterIsAccessible - helper methods always have access
86+
true, // SetterIsAccessible - helper methods always have access
87+
false // IsShallowClone - helper methods are never shallow
8788
);
8889

8990
if (!nestedTypes.ContainsKey(model.TypeFullName))
@@ -140,7 +141,8 @@ public static void Collect(
140141
false, // IsNullable
141142
true, // HasGetter - helper methods always have access
142143
true, // HasSetter - helper methods always have access
143-
true // SetterIsAccessible - helper methods always have access
144+
true, // SetterIsAccessible - helper methods always have access
145+
false // IsShallowClone - helper methods are never shallow
144146
);
145147

146148
if (!nestedTypes.ContainsKey(model.TypeFullName))

0 commit comments

Comments
 (0)