diff --git a/LICENSE.txt b/LICENSE.txt
index c85c401..12c11b7 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) [2023] [Pritom Purkayasta]
+Copyright (c) [2025] [Pritom Purkayasta]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/TODO.md b/TODO.md
index 12e47d7..15f06ee 100644
--- a/TODO.md
+++ b/TODO.md
@@ -5,7 +5,3 @@
- [ ] Enum To String Converter function.
- [ ] Mapper
- [ ] Object Mapper Attribute with Source Code Generation.
-
-
-- [ ] Rewrite the unit test and test only the core logic part.
-- [ ] Upload into the nuget.
\ No newline at end of file
diff --git a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj
index bcfb76b..f16982c 100644
--- a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj
+++ b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj
@@ -1,7 +1,7 @@
ο»Ώ
- netstandard2.1; net6.0; net8.0
+ netstandard2.1; net6.0; net8.0; net9.0
Latest
enable
enable
@@ -52,8 +52,8 @@
-
-
+
+
diff --git a/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs
deleted file mode 100644
index 3161723..0000000
--- a/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Collections.Immutable;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-
-namespace UtilityVerse.Copy.Analyzers;
-
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public class MissingPartialErrorAnalyzer : DiagnosticAnalyzer
-{
- private static readonly DiagnosticDescriptor MissingPartialModifierRule = new(
- id: "UV001",
- title: "Type must be marked as partial for DeepCopy/ShallowCopy generation",
- messageFormat: "Type '{0}' must be marked as 'partial' to support DeepCopy/ShallowCopy generation",
- category: "UtilityVerse.Copy",
- DiagnosticSeverity.Error,
- isEnabledByDefault: true,
- description: "UtilityVerse.Copy requires all target types to be marked with 'partial' so the DeepCopy() or ShallowCopy() method can be injected."
- );
-
- public override ImmutableArray SupportedDiagnostics => [MissingPartialModifierRule];
-
- public override void Initialize(AnalysisContext context)
- {
- // Configure analysis for syntax trees
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
-
- context.RegisterSyntaxNodeAction(AnalyzeTypeDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration);
- }
-
- private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context)
- {
- var typeDecl = (TypeDeclarationSyntax)context.Node;
-
- // Skip if already marked partial
- if (typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
- return;
-
- // Skip if not user-defined (e.g., external/metadata references)
- var symbol = context.SemanticModel.GetDeclaredSymbol(typeDecl);
- if (symbol is null || symbol.DeclaringSyntaxReferences.Length == 0)
- return;
-
- // Optionally: check if it's used for DeepCopy somehow
- // Example: skip types with [GeneratedCode] or [CompilerGenerated]
- if (symbol.GetAttributes().Any(attr =>
- attr.AttributeClass?.ToDisplayString() is "System.CodeDom.Compiler.GeneratedCodeAttribute" or "System.Runtime.CompilerServices.CompilerGeneratedAttribute"))
- return;
-
- var diagnostic = Diagnostic.Create(MissingPartialModifierRule, typeDecl.Identifier.GetLocation(), symbol.Name);
- context.ReportDiagnostic(diagnostic);
- }
-}
\ No newline at end of file
diff --git a/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs
deleted file mode 100644
index 1c3a407..0000000
--- a/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-using System.Collections.Immutable;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-
-namespace UtilityVerse.Copy.Analyzers;
-
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public class NestedTypeErrorAnalyzer : DiagnosticAnalyzer
-{
- private static readonly DiagnosticDescriptor DeepRule = new(
- id: "UV002",
- title: "Nested type missing DeepCopy attribute",
- messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have DeepCopy attribute or implement IDeepCopy",
- category: "UtilityVerse.Copy",
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true);
-
- private static readonly DiagnosticDescriptor ShallowRule = new DiagnosticDescriptor(
- id: "UV003",
- title: "Nested type missing ShallowCopy attribute",
- messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have ShallowCopy attribute or implement IShallowCopy",
- category: "UtilityVerse.Copy",
- defaultSeverity: DiagnosticSeverity.Error,
- isEnabledByDefault: true);
-
- public override ImmutableArray SupportedDiagnostics =>
- [DeepRule, ShallowRule];
-
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
-
- context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
- }
-
- private void AnalyzeNamedType(SymbolAnalysisContext context)
- {
- var namedType = (INamedTypeSymbol)context.Symbol;
-
- // Only analyze public partial types (assuming generator requires partial)
- if (namedType.DeclaredAccessibility != Accessibility.Public)
- return;
-
- if (!namedType.DeclaringSyntaxReferences.Any(syntaxRef =>
- syntaxRef.GetSyntax() is TypeDeclarationSyntax tds &&
- tds.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))))
- return;
-
- // Check if type opts into DeepCopy or ShallowCopy
- bool hasDeepCopy = Helper.HasDeepCopyOptIn(namedType);
- bool hasShallowCopy = Helper.HasShallowCopyOptIn(namedType);
-
- if (!hasDeepCopy && !hasShallowCopy)
- return; // type itself not opted in - no need to check nested types
-
- // Get all properties including inherited
- var properties = namedType.GetMembers().OfType().Where(p => !p.IsStatic);
-
- foreach (var prop in properties)
- {
- if (prop.Type is not INamedTypeSymbol propType)
- {
- continue;
- }
- // Skip primitives and enums
- if (Helper.IsTrulyPrimitive(propType))
- continue;
-
- if (propType.IsGenericType)
- {
- foreach (var typeArg in propType.TypeArguments.OfType())
- {
- if (Helper.IsTrulyPrimitive(typeArg))
- continue;
-
- if (hasDeepCopy && !Helper.HasDeepCopyOptIn(typeArg))
- {
- var diagnostic = Diagnostic.Create(
- DeepRule,
- prop.Locations.FirstOrDefault(),
- prop.Name, namedType.Name, typeArg.Name);
- context.ReportDiagnostic(diagnostic);
- }
-
- if (hasShallowCopy && !Helper.HasShallowCopyOptIn(typeArg))
- {
- var diagnostic = Diagnostic.Create(
- ShallowRule,
- prop.Locations.FirstOrDefault(),
- prop.Name, namedType.Name, typeArg.Name);
- context.ReportDiagnostic(diagnostic);
- }
- }
- }
- else
- {
- // Handle non-generic nested types
- if (hasDeepCopy && !Helper.HasDeepCopyOptIn(propType))
- {
- var diagnostic = Diagnostic.Create(
- DeepRule,
- prop.Locations.FirstOrDefault(),
- prop.Name, namedType.Name, propType.Name);
- context.ReportDiagnostic(diagnostic);
- }
-
- if (hasShallowCopy && !Helper.HasShallowCopyOptIn(propType))
- {
- var diagnostic = Diagnostic.Create(
- ShallowRule,
- prop.Locations.FirstOrDefault(),
- prop.Name, namedType.Name, propType.Name);
- context.ReportDiagnostic(diagnostic);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/UtilityVerse.Copy/Analyzers/Rules.cs b/src/UtilityVerse.Copy/Analyzers/Rules.cs
new file mode 100644
index 0000000..5bb93a9
--- /dev/null
+++ b/src/UtilityVerse.Copy/Analyzers/Rules.cs
@@ -0,0 +1,30 @@
+ο»Ώusing Microsoft.CodeAnalysis;
+
+namespace UtilityVerse.Copy.Analyzers;
+
+internal static class Rules
+{
+ internal static readonly DiagnosticDescriptor MissingPartialRule = new(
+ id: "UV001",
+ title: "Type must be marked as partial",
+ messageFormat: "Type '{0}' must be marked as 'partial' to support DeepCopy/ShallowCopy generation",
+ category: "UtilityVerse.Copy",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ internal static readonly DiagnosticDescriptor DeepRule = new(
+ id: "UV002",
+ title: "Nested type missing DeepCopy attribute",
+ messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have DeepCopy attribute or implement IDeepCopy",
+ category: "UtilityVerse.Copy",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ internal static readonly DiagnosticDescriptor ShallowRule = new(
+ id: "UV003",
+ title: "Nested type missing ShallowCopy attribute",
+ messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have ShallowCopy attribute or implement IShallowCopy",
+ category: "UtilityVerse.Copy",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+}
\ No newline at end of file
diff --git a/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs
new file mode 100644
index 0000000..6207a06
--- /dev/null
+++ b/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs
@@ -0,0 +1,133 @@
+ο»Ώusing System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace UtilityVerse.Copy.Analyzers;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class ValidationAnalyzer : DiagnosticAnalyzer
+{
+ public override ImmutableArray SupportedDiagnostics =>
+ [Rules.MissingPartialRule, Rules.DeepRule, Rules.ShallowRule];
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(compilationContext =>
+ {
+ compilationContext.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
+ });
+ }
+
+ private static void AnalyzeNamedType(SymbolAnalysisContext context)
+ {
+ if (context.Symbol is not INamedTypeSymbol namedType)
+ return;
+
+ if (namedType.TypeKind is not (TypeKind.Class or TypeKind.Struct))
+ return;
+
+ if (namedType.GetAttributes().Any(attr =>
+ attr.AttributeClass?.ToDisplayString() is
+ "System.CodeDom.Compiler.GeneratedCodeAttribute" or
+ "System.Runtime.CompilerServices.CompilerGeneratedAttribute"))
+ return;
+
+ var syntaxRef = namedType.DeclaringSyntaxReferences.FirstOrDefault();
+ if (syntaxRef?.GetSyntax() is not TypeDeclarationSyntax typeSyntax)
+ return;
+
+ var hasDeepCopy = Helper.HasDeepCopyOptIn(namedType);
+ var hasShallowCopy = Helper.HasShallowCopyOptIn(namedType);
+
+ if ((hasDeepCopy || hasShallowCopy) && !Helper.IsPartial(namedType))
+ {
+ var diagnostic = Diagnostic.Create(Rules.MissingPartialRule, typeSyntax.Identifier.GetLocation(), namedType.Name);
+ context.ReportDiagnostic(diagnostic);
+ }
+
+ if (!hasDeepCopy && !hasShallowCopy)
+ return;
+
+ foreach (var prop in Helper.GetAllProperties(namedType).Where(p => !p.IsStatic))
+ {
+ AnalyzeTypeRecursive(
+ context,
+ prop.Type,
+ prop.Locations.FirstOrDefault() ?? typeSyntax.Identifier.GetLocation(),
+ prop.Name,
+ namedType,
+ hasDeepCopy,
+ hasShallowCopy
+ );
+ }
+ }
+
+ private static void AnalyzeTypeRecursive(
+ SymbolAnalysisContext context,
+ ITypeSymbol type,
+ Location diagnosticLocation,
+ string propertyName,
+ INamedTypeSymbol parentType,
+ bool requireDeepCopy,
+ bool requireShallowCopy)
+ {
+ if (type is not INamedTypeSymbol namedType)
+ return;
+
+ var unwrapped = UnwrapNullable(namedType);
+
+ if (Helper.IsTrulyPrimitive(unwrapped))
+ return;
+
+ if (Helper.IsGenericCollection(unwrapped.OriginalDefinition.ToDisplayString(), out _))
+ {
+ foreach (var typeArg in unwrapped.TypeArguments.OfType())
+ {
+ AnalyzeTypeRecursive(context, typeArg, diagnosticLocation, propertyName, parentType, requireDeepCopy, requireShallowCopy);
+ }
+ return;
+ }
+
+ if (requireDeepCopy && !Helper.HasDeepCopyOptIn(unwrapped))
+ {
+ var diag = Diagnostic.Create(Rules.DeepRule, diagnosticLocation,
+ propertyName, parentType.Name, unwrapped.Name);
+ context.ReportDiagnostic(diag);
+ }
+
+ if (requireShallowCopy && !Helper.HasShallowCopyOptIn(unwrapped))
+ {
+ var diag = Diagnostic.Create(Rules.ShallowRule, diagnosticLocation,
+ propertyName, parentType.Name, unwrapped.Name);
+ context.ReportDiagnostic(diag);
+ }
+ }
+
+ private static INamedTypeSymbol UnwrapNullable(INamedTypeSymbol type)
+ {
+ return type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
+ type.TypeArguments.FirstOrDefault() is INamedTypeSymbol underlying
+ ? underlying
+ : type;
+ }
+
+ private static bool IsInsideCopyEnabledPartialParent(INamedTypeSymbol? symbol)
+ {
+ while (symbol is not null)
+ {
+ if (!Helper.IsPartial(symbol))
+ return false;
+
+ if (Helper.HasDeepCopyOptIn(symbol) || Helper.HasShallowCopyOptIn(symbol))
+ return true;
+
+ symbol = symbol.ContainingType;
+ }
+ return false;
+ }
+}
diff --git a/src/UtilityVerse.Copy/Helper.cs b/src/UtilityVerse.Copy/Helper.cs
index 7a0a838..83ce90d 100644
--- a/src/UtilityVerse.Copy/Helper.cs
+++ b/src/UtilityVerse.Copy/Helper.cs
@@ -1,9 +1,3 @@
-///
-/// Author: Pritom Purkayasta
-// Copyright (c) Pritom Purkayasta All rights reserved.
-// FREE TO USE TO CONNECT THE WORLD
-///
-
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
@@ -22,19 +16,19 @@ internal static bool IsTrulyPrimitive(ITypeSymbol symbol)
return symbol.SpecialType switch
{
SpecialType.System_Boolean or
- SpecialType.System_Byte or
- SpecialType.System_SByte or
- SpecialType.System_Int16 or
- SpecialType.System_UInt16 or
- SpecialType.System_Int32 or
- SpecialType.System_UInt32 or
- SpecialType.System_Int64 or
- SpecialType.System_UInt64 or
- SpecialType.System_Single or
- SpecialType.System_Double or
- SpecialType.System_Char or
- SpecialType.System_Enum or
- SpecialType.System_String => true,
+ SpecialType.System_Byte or
+ SpecialType.System_SByte or
+ SpecialType.System_Int16 or
+ SpecialType.System_UInt16 or
+ SpecialType.System_Int32 or
+ SpecialType.System_UInt32 or
+ SpecialType.System_Int64 or
+ SpecialType.System_UInt64 or
+ SpecialType.System_Single or
+ SpecialType.System_Double or
+ SpecialType.System_Char or
+ SpecialType.System_Enum or
+ SpecialType.System_String => true,
_ => false
};
}
@@ -43,7 +37,6 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind)
{
kind = originalDef switch
{
- // List-like collections
"System.Collections.Generic.List" => "List",
"System.Collections.Generic.IList" => "List",
"System.Collections.ObjectModel.Collection" => "List",
@@ -51,30 +44,25 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind)
"System.Collections.ObjectModel.ObservableCollection" => "List",
"System.ComponentModel.BindingList" => "List",
"System.Collections.Immutable.ImmutableList" => "List",
- "System.Collections.Generic.ReadOnlyList" => "List", // custom/unofficial
+ "System.Collections.Generic.ReadOnlyList" => "List",
- // Enumerable-like collections
"System.Collections.Generic.IEnumerable" => "Enumerable",
"System.Collections.Generic.ICollection" => "Enumerable",
"System.Collections.Generic.IReadOnlyCollection" => "Enumerable",
"System.Collections.Generic.IReadOnlyList" => "Enumerable",
- // Hash-based collections
"System.Collections.Generic.HashSet" => "HashSet",
- // Dictionary-like collections
"System.Collections.Generic.Dictionary" => "Dictionary",
"System.Collections.Frozen.FrozenDictionary" => "Dictionary",
"System.Collections.Concurrent.ConcurrentDictionary" => "Dictionary",
- // Immutable
"System.Collections.Immutable.ImmutableArray" => "List",
"System.Collections.Immutable.ImmutableSortedSet" => "HashSet",
"System.Collections.Immutable.ImmutableHashSet" => "HashSet",
"System.Collections.Immutable.ImmutableDictionary" => "Dictionary",
"System.Collections.Immutable.ImmutableSortedDictionary" => "Dictionary",
- // Queue / Stack
"System.Collections.Generic.Queue" => "Queue",
"System.Collections.Generic.Stack" => "Stack",
@@ -107,13 +95,22 @@ internal static bool IsPartial(INamedTypeSymbol typeSymbol)
internal static bool HasDeepCopyOptIn(INamedTypeSymbol typeSymbol)
{
- return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.DeepCopy") ||
- typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IDeepCopy");
+ return typeSymbol.GetAttributes().Any(a =>
+ SymbolEquals(a.AttributeClass, "UtilityVerse.Copy.DeepCopy")) ||
+ typeSymbol.AllInterfaces.Any(i =>
+ SymbolEquals(i, "UtilityVerse.Copy.IDeepCopy"));
}
internal static bool HasShallowCopyOptIn(INamedTypeSymbol typeSymbol)
{
- return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.ShallowCopy") ||
- typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IShallowCopy");
+ return typeSymbol.GetAttributes().Any(a =>
+ SymbolEquals(a.AttributeClass, "UtilityVerse.Copy.ShallowCopy")) ||
+ typeSymbol.AllInterfaces.Any(i =>
+ SymbolEquals(i, "UtilityVerse.Copy.IShallowCopy"));
+ }
+
+ private static bool SymbolEquals(ISymbol? symbol, string fullyQualifiedMetadataName)
+ {
+ return symbol?.ToDisplayString() == fullyQualifiedMetadataName;
}
-}
\ No newline at end of file
+}
diff --git a/src/UtilityVerse.Copy/README.md b/src/UtilityVerse.Copy/README.md
index c3c24e7..b9fb7b2 100644
--- a/src/UtilityVerse.Copy/README.md
+++ b/src/UtilityVerse.Copy/README.md
@@ -1,174 +1,183 @@
+
---
# π¦ UtilityVerse.Copy β Source Generator for DeepCopy and ShallowCopy in C\#
-π **UtilityVerse.Copy** is a Roslyn-based source generator that automatically creates `DeepCopy()` and `ShallowCopy()` methods for your models. Eliminate repetitive boilerplate and enjoy clean, maintainable code with zero runtime dependencies.
-
+ 
---
-## β¨ Features
+## π What is UtilityVerse.Copy?
-* β
Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods at compile-time
-* β
No runtime overhead β it's all generated in the background by Roslyn
-* β
Supports both `[Attribute]`-based and `interface`-based opt-in mechanisms
-* β
Works great for DTOs, ViewModels, and plain C# objects
-* β
Support for common collection types, arrays, tuples, and more
+**UtilityVerse.Copy** is a Roslyn-based source generator for C#. It automatically creates `DeepCopy()` and `ShallowCopy()` methods for your classes and structs at compile-time. With zero runtime dependencies, your copy logic is safe, efficient, and maintainable β without writing repetitive code.
---
+## β Why Use It?
-## > Give it a star if you like the project. π π π
+* Copying objects (deep or shallow) usually requires verbose, error-prone boilerplate.
+* UtilityVerse.Copy removes that burden by generating fully-typed copy methods at compile-time using Roslyn.
+* No runtime reflection, no magic β just pure, reliable, compiled code.
+* Perfect for:
+ * DTOs
+ * ViewModels
+ * Configuration models
+ * Game entities
+ * Domain models needing clean clone operations
---
-## π¦ Installation
+## β¨ Features
-Install the NuGet package:
+* β
Auto-generates `DeepCopy()` and/or `ShallowCopy()` methods
+* β
Pure compile-time code generation (no runtime dependencies)
+* β
Supports both `[Attributes]` and `Marker Interfaces`
+* β
Handles:
-```bash
-dotnet add package UtilityVerse.Copy
-```
+ * Common collections (List, HashSet, Dictionary, etc.)
+ * Arrays, Tuples, ValueTuples
+ * Primitive types, strings, and nested objects
+ * Records, classes, and structs (as long as they are `partial`)
+* β
Safe β generated code is separate and non-intrusive
---
-## π οΈ How It Works
+## π API β What You Need to Use
-> **Important**: Your classes must be marked `partial` for the generator to emit code successfully.
+### 1οΈβ£ Attributes
-### β
Option 1: Use Attributes
+Decorate your classes/structs using:
-Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct:
+* `[DeepCopy]` β to generate a recursive deep copy method.
+* `[ShallowCopy]` β to generate a simple shallow copy.
+
+Example:
```csharp
using UtilityVerse.Copy;
-[ShallowCopy]
-public partial class Person
+[DeepCopy]
+public partial class Product
{
public string Name { get; set; }
- public int Age { get; set; }
+ public decimal Price { get; set; }
}
```
-This generates a `ShallowCopy()` method at compile-time:
+---
-```csharp
-public Person ShallowCopy()
-{
- return (Person)this.MemberWiseClone();
-}
-```
+### 2οΈβ£ Marker Interfaces
-For deep copy:
+If you prefer code-first opt-in:
+
+* Implement `IDeepCopy` for deep copy generation.
+* Implement `IShallowCopy` for shallow copy generation.
+
+Example:
```csharp
using UtilityVerse.Copy;
-[DeepCopy]
-public partial class Person
+
+public partial class Product : IDeepCopy
{
public string Name { get; set; }
- public int Age { get; set; }
+ public decimal Price { get; set; }
}
```
-Generates a deep recursive `DeepCopy()` method that copies nested references and collections.
+---
-```csharp
-public Person DeepCopy()
-{
- return new Person
- {
- Name = this.Name,
- Age = this.Age
- };
-}
-```
+> **Important:**
+> Your class or struct **must be marked `partial`** for the source generator to work.
---
-### β
Option 2: Use Marker Interfaces
+## π Tutorial β How to Use
-Prefer no attributes? Just implement the marker interfaces:
+### Step 1οΈβ£ β Install via NuGet
+
+```bash
+dotnet add package UtilityVerse.Copy
+```
+
+---
+
+### Step 2οΈβ£ β Add `[DeepCopy]` or `[ShallowCopy]`
```csharp
using UtilityVerse.Copy;
-public partial class Person : IShallowCopy
+
+[DeepCopy]
+public partial class User
{
- public string Name { get; set; }
- public int Age { get; set; }
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public List Addresses { get; set; } = [];
}
-```
-```csharp
-using UtilityVerse.Copy;
-public partial class Person : IDeepCopy
+[DeepCopy]
+public partial class Address
{
- public string Name { get; set; }
- public int Age { get; set; }
+ public string? StreetNumber { get; set; }
}
```
-The generator will handle the rest automatically!
-
---
-## π Supported Types
+### Step 3οΈβ£ β Generated Code (Example)
-* β
Primitives and strings
-* β
Arrays
-* β
Tuples and `ValueTuple`
-* β
Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc.
-* β
Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc.
-* β
Record types, classes, structs (as long as they are `partial`)
-* β
Nested properties recursively copied in `DeepCopy()`
-
----
-
-## π§ͺ Sample Output
-
-Given this class:
+`DeepCopy()` for `User` will be automatically generated:
```csharp
-[DeepCopy]
-public partial class User
+public User DeepCopy()
{
- public string Name { get; set; }
- public List Addresses { get; set; }
+ return new User
+ {
+ Id = this.Id,
+ Name = this.Name,
+ Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList()
+ };
}
```
-Generated deep copy:
+Similarly for `Address`:
```csharp
-public User DeepCopy()
+public Address DeepCopy()
{
- return new User
+ return new Address
{
- Name = this.Name,
- Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList()
+ StreetNumber = this.StreetNumber
};
}
```
---
-## π Safe and Reliable
+### Step 4οΈβ£ β Use It!
-* π‘ **Partial**: Wonβt overwrite your code β generated code lives alongside your class
-* π§Ύ **Readable**: Generated files are emitted to the intermediate folder (obj)
-* βοΈ **Non-Intrusive**: No reflection, no extra dependencies
+```csharp
+var userClone = existingUser.DeepCopy();
+```
---
-## π License
+## π Supported Types
-This project is licensed under the [MIT License](LICENSE).
+* β
Primitives (`int`, `string`, `bool`, etc.)
+* β
Arrays
+* β
Tuples & ValueTuples
+* β
Generic collections (`List`, `Dictionary`, etc.)
+* β
Records, classes, structs (`partial` required)
+* β
Deep recursive copying of nested properties
---
-## π€ Contributing
-We welcome contributions! Please see the [CONTRIBUTE.md](CONTRIBUTE.md) file for guidelines.
+## π£ Like It? Star It! β
+
+If you find UtilityVerse.Copy useful, give the repository a β and help others discover it!
+
+---
----
\ No newline at end of file
+Let me know if you'd like this structured as a template file or want me to generate sample output code files directly.
diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj
index 3e5aee4..ad62653 100644
--- a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj
+++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj
@@ -15,7 +15,7 @@
Copy
UtilityVerse.Copy
- 0.3.0
+ 0.4.0
Pritom Purkayasta
A Roslyn source generator for generating shallow copy and deep copy for class,
records and structs
diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.sln b/src/UtilityVerse.Copy/UtilityVerse.Copy.sln
deleted file mode 100644
index 5f63515..0000000
--- a/src/UtilityVerse.Copy/UtilityVerse.Copy.sln
+++ /dev/null
@@ -1,24 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.5.2.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Copy", "UtilityVerse.Copy.csproj", "{381427B0-86D7-C4BF-ADDE-F56BD66B638B}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {54E540C6-9E0C-4569-B917-30077F35B028}
- EndGlobalSection
-EndGlobal
diff --git a/src/UtilityVerse.Copy/nuget.md b/src/UtilityVerse.Copy/nuget.md
index 21e42f2..e84e0bb 100644
--- a/src/UtilityVerse.Copy/nuget.md
+++ b/src/UtilityVerse.Copy/nuget.md
@@ -17,146 +17,5 @@
---
-## > Give it a star if you like the project. π π π
-
----
-
-## π¦ Installation
-
-Install the NuGet package:
-
-```bash
-dotnet add package UtilityVerse.Copy
-```
-
----
-
-## π οΈ How It Works
-
-> **Important**: Your classes must be marked `partial` for the generator to emit code successfully.
-
-### β
Option 1: Use Attributes
-
-Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct:
-
-```csharp
-using UtilityVerse.Copy;
-
-[ShallowCopy]
-public partial class Person
-{
- public string Name { get; set; }
- public int Age { get; set; }
-}
-```
-
-This generates a `ShallowCopy()` method at compile-time:
-
-```csharp
-public Person ShallowCopy()
-{
- return (Person)this.MemberWiseClone();
-}
-```
-
-For deep copy:
-
-```csharp
-using UtilityVerse.Copy;
-[DeepCopy]
-public partial class Person
-{
- public string Name { get; set; }
- public int Age { get; set; }
-}
-```
-
-Generates a deep recursive `DeepCopy()` method that copies nested references and collections.
-
-```csharp
-public Person DeepCopy()
-{
- return new Person
- {
- Name = this.Name,
- Age = this.Age
- };
-}
-```
-
----
-
-### β
Option 2: Use Marker Interfaces
-
-Prefer no attributes? Just implement the marker interfaces:
-
-```csharp
-using UtilityVerse.Copy;
-public partial class Person : IShallowCopy
-{
- public string Name { get; set; }
- public int Age { get; set; }
-}
-```
-
-```csharp
-using UtilityVerse.Copy;
-public partial class Person : IDeepCopy
-{
- public string Name { get; set; }
- public int Age { get; set; }
-}
-```
-
-The generator will handle the rest automatically!
-
----
-
-## π Supported Types
-
-* β
Primitives and strings
-* β
Arrays
-* β
Tuples and `ValueTuple`
-* β
Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc.
-* β
Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc.
-* β
Record types, classes, structs (as long as they are `partial`)
-* β
Nested properties recursively copied in `DeepCopy()`
-
----
-
-## π§ͺ Sample Output
-
-Given this class:
-
-```csharp
-[DeepCopy]
-public partial class User
-{
- public string Name { get; set; }
- public List Addresses { get; set; }
-}
-```
-
-Generated deep copy:
-
-```csharp
-public User DeepCopy()
-{
- return new User
- {
- Name = this.Name,
- Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList()
- };
-}
-```
-
----
-
-## π Safe and Reliable
-
-* π‘ **Partial**: Wonβt overwrite your code β generated code lives alongside your class
-* π§Ύ **Readable**: Generated files are emitted to the intermediate folder (obj)
-* βοΈ **Non-Intrusive**: No reflection, no extra dependencies
-
----
\ No newline at end of file
+> Code is fully opensource and more details can be found here at: [Github](https://github.com/purkayasta/TheUtilityVerse/blob/development/src/UtilityVerse.Copy/README.md)
\ No newline at end of file
diff --git a/src/UtilityVerse/breaking-changes.md b/src/UtilityVerse/breaking-changes.md
index f561dd3..bd1d520 100644
--- a/src/UtilityVerse/breaking-changes.md
+++ b/src/UtilityVerse/breaking-changes.md
@@ -1,5 +1,4 @@
# Breaking Changes
-- [x] Upgraded to .net8.0 and .netstandard2.1
-- [x] Instead of throwing exception, returning a data type where errors are given in a text so that developer can log it and handle their own way.
-- [x] Remove Nullability.
\ No newline at end of file
+- [x] Upgraded to .net9.0 and .netstandard2.1
+- [x] Instead of throwing exception, returning a data type where errors are given in a text so that developer can log it and handle their own way.
\ No newline at end of file
diff --git a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj
index d863d7b..6bc15f5 100644
--- a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj
+++ b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj
@@ -9,13 +9,13 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all