Skip to content

Commit bc3e67a

Browse files
How to clone a list: retarget net10.0, collection expression and GetRange, remove broken ToppingsList clone (#2210)
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 fac1227 commit bc3e67a

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)