Как копировать / клонировать записи в C# 9?

Спецификация функции записей C# 9 включает следующее:

Тип записи содержит два копирующих члена:

Конструктор, принимающий единственный аргумент типа записи. Он называется "конструктором копирования". Синтезированный метод "клонирования" общедоступного экземпляра без параметров с именем, зарезервированным компилятором.

Но я не могу назвать ни одного из этих двух копирующих членов:

public record R(int A);
// ...
var r2 = new R(r); // ERROR: inaccessible due to protection level
var r3 = r.Clone(); // ERROR: R does not contain a definition for Clone

Исходя из этого, я понимаю, что конструктор защищен и, следовательно, к нему нельзя получить доступ за пределами иерархии наследования записи. Итак, у нас остался такой код:

var r4 = r with { };

А как насчет клонирования? Метод клонирования является общедоступным в соответствии со спецификацией выше. Но как его зовут? Или это фактически случайная строка, поэтому ее не следует вызывать вне иерархии наследования записи? Если да, то каков правильный способ глубокого копирования записей? Из спецификации видно, что можно создать собственный метод клонирования. Так ли это, и каков будет пример того, как это должно работать?

3 ответа

Решение

А как насчет клонирования?

var r4 = r with { };

выполняет неглубокий клон на r.

Метод клонирования является общедоступным в соответствии со спецификацией выше. Но как его зовут?

У компилятора C# есть довольно распространенный прием, когда он дает сгенерированные имена членов, которые недопустимы в C#, но допустимы в IL, поэтому их нельзя вызывать, кроме как из компилятора, даже если они общедоступны. В этом случае имя Clone метод <Clone>$.

Если да, то каков правильный способ глубокого копирования записей?

Глубокое копирование вам не повезло. Однако, поскольку в идеале записи должны быть неизменяемыми, на практике не должно быть разницы между мелкой копией, глубокой копией и исходным экземпляром.

Из спецификации видно, что можно создать собственный метод клонирования. Так ли это, и каков будет пример того, как это должно работать?

К сожалению, это не годится для C# 9, но есть большая вероятность, что это будет в C# 10.

Чтобы выполнить глубокое клонирование, вы добавляете в запись собственный конструктор копирования:

      public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public record EmployeeRecord(int UniqueId, Person Employee)
{

    // Custom copy constructor (Should be protected or private)
    protected EmployeeRecord(EmployeeRecord other)
    {
        UniqueId = other.UniqueId;

        // Do deep copy stuff
        Employee = new Person 
        { 
            FirstName = Employee.FirstName, 
            LastName = Employee.LastName 
        };
    }
}

В этом случае компилятор не будет генерировать свой собственный. Затем вы используете ключевое слово «с»:

      var emp1 = new EmployeeRecord(100, 
    new Person { FirstName = "John", LastName = "Doe" });

var emp2 = emp1 with { };

Что касается клона записи, да, похоже, что with создаст для вас глубокий клон, как в примере ниже:

ПРИМЕР ЗАПИСИ:

using System;
                    
public class Program
{
    public static void Main()
    {
        var person1 = new Person
        {
            Hair = "brown",
            Skin = "tawny"
        };
        
        var inhabitant1 = new Inhabitant
        {
            Name = "Moreno",
            Profile = person1
        };
        var inhabitant2 = new Inhabitant
        {
            Name = "Branquinho",
            Profile = inhabitant1.Profile with { Skin = "sparkle" }
        };
        
        Console.WriteLine("--- Brown, Tawny ---");
        Console.WriteLine(inhabitant1.ToString());
        Console.WriteLine(inhabitant2.ToString());
        
        inhabitant1.Profile.Hair = "painted";
        
        Console.WriteLine("\n--- Painted, Tawny ---");
        Console.WriteLine(inhabitant1.ToString());
        Console.WriteLine(inhabitant2.ToString());
        
        inhabitant2.Profile.Skin = "sunburn";
        
        Console.WriteLine("\n--- Painted, Tawny ---");
        Console.WriteLine(inhabitant1.ToString());
        Console.WriteLine(inhabitant2.ToString());
        
        person1.Skin = "green";
        person1.Hair = "green";
        
        Console.WriteLine("\n--- Green, Green ---");
        Console.WriteLine(inhabitant1.ToString());
        Console.WriteLine(inhabitant2.ToString());
        
        inhabitant1.Profile = new Person
        {
            Skin = "blue",
            Hair = "blue"
        };
        
        Console.WriteLine("\n--- Blue, Blue ---");
        Console.WriteLine(inhabitant1.ToString());
        Console.WriteLine(inhabitant2.ToString());
        
        
    }
    
    public record Inhabitant()
    {
        public string Name { get; set; }
        public Person Profile { get; set; }
    }
    
    public record Person()
    {
        public string Hair { get; set; }
        public string Skin { get; set; }
    }
}

Вывод:

--- Brown, Tawny ---
Inhabitant { Name = Moreno, Profile = Person { Hair = brown, Skin = tawny } }
Inhabitant { Name = Branquinho, Profile = Person { Hair = brown, Skin = sparkle } }

--- Painted, Tawny ---
Inhabitant { Name = Moreno, Profile = Person { Hair = painted, Skin = tawny } }
Inhabitant { Name = Branquinho, Profile = Person { Hair = brown, Skin = sparkle } }

--- Painted, Tawny ---
Inhabitant { Name = Moreno, Profile = Person { Hair = painted, Skin = tawny } }
Inhabitant { Name = Branquinho, Profile = Person { Hair = brown, Skin = sunburn } }

--- Green, Green ---
Inhabitant { Name = Moreno, Profile = Person { Hair = green, Skin = green } }
Inhabitant { Name = Branquinho, Profile = Person { Hair = brown, Skin = sunburn } }

--- Blue, Blue ---
Inhabitant { Name = Moreno, Profile = Person { Hair = blue, Skin = blue } }
Inhabitant { Name = Branquinho, Profile = Person { Hair = brown, Skin = sunburn } }

Более того, как мы видим, мы также можем изменить неизменяемость свойств записи "из коробки", если мы явно объявим свойства записи. Мы также могли бы написать свои собственные конструкторы.

Если бы мы проделали вышеуказанные операции с обычным классом, то копия была бы ожидаемым мелким клоном.

ПРИМЕР КЛАССА:

using System;
                    
public class Program
{
    public static void Main()
    {
        var person1 = new Person
        {
            Hair = "brown",
            Skin = "tawny"
        };
        
        var inhabitant1 = new Inhabitant
        {
            Name = "Moreno",
            Profile = person1
        };
        var inhabitant2 = new Inhabitant
        {
            Name = "Branquinho",
            Profile = inhabitant1.Profile
        };
        inhabitant2.Profile.Skin = "sparkle";
        
        Console.WriteLine("--- Brown, Tawny ---");
        Console.WriteLine(inhabitant1.Profile.Hair + " | " + inhabitant1.Profile.Skin);
        Console.WriteLine(inhabitant2.Profile.Hair + " | " + inhabitant2.Profile.Skin);
        
        inhabitant1.Profile.Hair = "painted";
        
        Console.WriteLine("\n--- Painted, Tawny ---");
        Console.WriteLine(inhabitant1.Profile.Hair + " | " + inhabitant1.Profile.Skin);
        Console.WriteLine(inhabitant2.Profile.Hair + " | " + inhabitant2.Profile.Skin);
        
        inhabitant2.Profile.Skin = "sunburn";
        
        Console.WriteLine("\n--- Painted, Tawny ---");
        Console.WriteLine(inhabitant1.Profile.Hair + " | " + inhabitant1.Profile.Skin);
        Console.WriteLine(inhabitant2.Profile.Hair + " | " + inhabitant2.Profile.Skin);
        
        person1.Skin = "green";
        person1.Hair = "green";
        
        Console.WriteLine("\n--- Green, Green ---");
        Console.WriteLine(inhabitant1.Profile.Hair + " | " + inhabitant1.Profile.Skin);
        Console.WriteLine(inhabitant2.Profile.Hair + " | " + inhabitant2.Profile.Skin);
        
        inhabitant1.Profile = new Person
        {
            Skin = "blue",
            Hair = "blue"
        };
        
        Console.WriteLine("\n--- Blue, Blue ---");
        Console.WriteLine(inhabitant1.Profile.Hair + " | " + inhabitant1.Profile.Skin);
        Console.WriteLine(inhabitant2.Profile.Hair + " | " + inhabitant2.Profile.Skin);
    }
    
    public class Inhabitant
    {
        public string Name { get; set; }
        public Person Profile { get; set; }
    }
    
    public class Person
    {
        public string Hair { get; set; }
        public string Skin { get; set; }
    }
}

Вывод:

--- Brown, Tawny ---
brown | sparkle
brown | sparkle

--- Painted, Tawny ---
painted | sparkle
painted | sparkle

--- Painted, Tawny ---
painted | sunburn
painted | sunburn

--- Green, Green ---
green | green
green | green

--- Blue, Blue ---
blue | blue
green | green
Другие вопросы по тегам