Skip to content

Commit 76f4e74

Browse files
How to clone a list: retarget net10.0, collection expression and GetRange, remove broken ToppingsList clone
Move both projects from net7.0 to net10.0 and update the test stack (xunit 2.9.3, runner 4.0.0, Test.Sdk 18.10.1, coverlet 10.0.1, FluentAssertions 7.2.2). Remove ToppingsList<T> and its test: its Clone() called MemberwiseClone(), which copies the private backing array by reference, so the clone shared storage with the original and was not a clone at all. Add a collection-expression clone and a GetRange clone with tests, build the copy-constructor deep copy as a one-line projection, and add a test proving the projected clone keeps its toppings after the original is cleared. Fix the ConverAll typo in the console output, make Pizza.Name and Pizza.Toppings required with [SetsRequiredMembers] on the copy constructor, and use First instead of FirstOrDefault. The folder now builds with 0 warnings and runs 11 of 11 tests.
1 parent e413527 commit 76f4e74

8 files changed

Lines changed: 68 additions & 60 deletions

File tree

‎collections-lists/HowToCloneAList/HowToCloneAList.Tests/HowToCloneAList.Tests.csproj‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net7.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77

88
<IsPackable>false</IsPackable>
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="FluentAssertions" Version="6.9.0" />
13-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
14-
<PackageReference Include="xunit" Version="2.4.2" />
15-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
12+
<PackageReference Include="FluentAssertions" Version="7.2.2" />
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
14+
<PackageReference Include="xunit" Version="2.9.3" />
15+
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
<PrivateAssets>all</PrivateAssets>
1818
</PackageReference>
19-
<PackageReference Include="coverlet.collector" Version="3.1.2">
19+
<PackageReference Include="coverlet.collector" Version="10.0.1">
2020
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2121
<PrivateAssets>all</PrivateAssets>
2222
</PackageReference>

‎collections-lists/HowToCloneAList/HowToCloneAList.Tests/PizzaTests.cs‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using FluentAssertions;
1+
using FluentAssertions;
22

33
namespace HowToCloneAList.Tests
44
{
@@ -60,5 +60,29 @@ public void GivenAValidPizza_WhenToStringMethodIsInvoked_ThenToStringMethodMetho
6060

6161
expectedOutput.Should().Be(margherita.ToString());
6262
}
63+
64+
[Fact]
65+
public void GivenAListOfPizzas_WhenProjectedThroughTheCopyConstructor_ThenTheCloneKeepsItsToppings()
66+
{
67+
var pizzas = new List<Pizza>
68+
{
69+
new Pizza
70+
{
71+
Name = "Margherita",
72+
Toppings = new List<string>
73+
{
74+
"Mozzarella",
75+
"Olive oil",
76+
"Basil"
77+
}
78+
}
79+
};
80+
81+
List<Pizza> clone = [.. pizzas.Select(p => new Pizza(p))];
82+
83+
pizzas[0].Toppings.Clear();
84+
85+
clone[0].Toppings.Should().HaveCount(3);
86+
}
6387
}
64-
}
88+
}

‎collections-lists/HowToCloneAList/HowToCloneAList.Tests/Tests.cs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,27 @@ public void GivenAValidList_WhenConvertAllMethodIsInvoked_ThenConvertAllMethodRe
5656

5757
listClone.Should().BeEquivalentTo(list);
5858
}
59+
60+
[Fact]
61+
public void GivenAValidList_WhenACollectionExpressionIsUsed_ThenTheCollectionExpressionReturnsNewListInstance()
62+
{
63+
var list = new List<string> { "one", "two", "three" };
64+
65+
List<string> listClone = [.. list];
66+
67+
listClone.Should().BeEquivalentTo(list);
68+
listClone.Should().NotBeSameAs(list);
69+
}
70+
71+
[Fact]
72+
public void GivenAValidList_WhenGetRangeMethodIsInvoked_ThenGetRangeMethodReturnsNewListInstance()
73+
{
74+
var list = new List<string> { "one", "two", "three" };
75+
76+
var listClone = list.GetRange(0, list.Count);
77+
78+
listClone.Should().BeEquivalentTo(list);
79+
listClone.Should().NotBeSameAs(list);
80+
}
5981
}
6082
}

‎collections-lists/HowToCloneAList/HowToCloneAList.Tests/ToppingsListTests.cs‎

Lines changed: 0 additions & 22 deletions
This file was deleted.

‎collections-lists/HowToCloneAList/HowToCloneAList/HowToCloneAList.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net7.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
</PropertyGroup>

‎collections-lists/HowToCloneAList/HowToCloneAList/Pizza.cs‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
namespace HowToCloneAList
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace HowToCloneAList
24
{
35
public class Pizza : ICloneable
46
{
57
public Pizza()
68
{
79
}
810

11+
[SetsRequiredMembers]
912
public Pizza(Pizza pizza)
1013
{
1114
Name = pizza.Name;
1215
Toppings = pizza.Toppings.ToList();
1316
}
1417

15-
public string Name { get; set; }
16-
public List<string> Toppings { get; set; }
18+
public required string Name { get; set; }
19+
public required List<string> Toppings { get; set; }
1720

1821
public object Clone()
1922
{

‎collections-lists/HowToCloneAList/HowToCloneAList/Program.cs‎

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,18 @@ static void Main(string[] args)
2424
var toppingsClonedWithConvertAll = toppings
2525
.ConvertAll(new Converter<string, string>(x => x));
2626

27-
var customToppingsList = new ToppingsList<string>
28-
{
29-
"Mozzarella",
30-
"Olive oil",
31-
"Basil"
32-
};
27+
List<string> toppingsClonedWithCollectionExpression = [.. toppings];
3328

34-
var toppingsClonedWithICloneable = (ToppingsList<string>)customToppingsList.Clone();
29+
var toppingsClonedWithGetRange = toppings.GetRange(0, toppings.Count);
3530

3631
Console.WriteLine("Original list: " + string.Join(", ", toppings));
3732
Console.WriteLine("Cloned with Constructor: " + string.Join(", ", toppingsClonedWithConstructor));
3833
Console.WriteLine("Cloned with CopyTo: " + string.Join(", ", toppingsClonedWithCopyTo));
3934
Console.WriteLine("Cloned with AddRange: " + string.Join(", ", toppingsClonedWithAddRange));
4035
Console.WriteLine("Cloned with ToList: " + string.Join(", ", toppingsClonedWithToList));
41-
Console.WriteLine("Cloned with ConverAll: " + string.Join(", ", toppingsClonedWithConvertAll));
42-
Console.WriteLine("Cloned with ICloneable: " + string.Join(", ", toppingsClonedWithICloneable));
36+
Console.WriteLine("Cloned with ConvertAll: " + string.Join(", ", toppingsClonedWithConvertAll));
37+
Console.WriteLine("Cloned with a collection expression: " + string.Join(", ", toppingsClonedWithCollectionExpression));
38+
Console.WriteLine("Cloned with GetRange: " + string.Join(", ", toppingsClonedWithGetRange));
4339

4440
var pizzas = new List<Pizza>
4541
{
@@ -74,15 +70,10 @@ static void Main(string[] args)
7470
pizzasClonedWithICloneable.Add((Pizza)pizza.Clone());
7571
}
7672

77-
var pizzasClonedWithCopyConstructor = new List<Pizza>();
78-
79-
foreach (var pizza in pizzas)
80-
{
81-
pizzasClonedWithCopyConstructor.Add(new Pizza(pizza));
82-
}
73+
List<Pizza> pizzasClonedWithCopyConstructor = [.. pizzas.Select(p => new Pizza(p))];
8374

8475
var margherita = pizzas
85-
.FirstOrDefault(x => x.Name == "Margherita");
76+
.First(x => x.Name == "Margherita");
8677

8778
margherita.Toppings.Clear();
8879

‎collections-lists/HowToCloneAList/HowToCloneAList/ToppingsList.cs‎

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)