1 Востаннє редагувалося leofun01 (29.10.2015 12:17:17)

Тема: System.ICloneable vs ICloneable<T>. Клонування об'єктів в C#.

Хочу створити інтерфейс ICloneable<...>, який буде містити один метод Clone(), і цей метод має повертати об'єкт, тип якого є таким як тип класу (або структури), який наслідує даний інтерфейс.

System.ICloneable запропонований Microsoft'ом мене не влаштовує, бо його метод повертає object.
Але мій інтерфейс буде наслідувати System.ICloneable (для підтримки логіки).

З використанням коваріації, мій інтерфейс мав би виглядати приблизно якось так:

public interface ICloneable<out T, ...> : System.ICloneable
    where T : ICloneable<T, ...>, ...
    ...
{
    new T Clone();
}

Найгарніший робочий варіант коду получивля наступний:

public interface ICloneable<out T> : System.ICloneable
    where T : ICloneable<T>
{
    new T Clone();
}

Але він не гарантує, що тип (тип-параметр) переданий в такий інтерфейс буде типом, який наслідує даний інтерфейс.
Приклад коду, де в ICloneable<T> передається не даний клас:

namespace MyNameSpace
{
    /// <summary> Задає метод клонування. </summary>
    /// <typeparam name="T"> Тип клонованого об'єкта. </typeparam>
    public interface ICloneable<out T> : System.ICloneable
        where T : ICloneable<T>
    {
        /// <summary> Повертає<returns> об'єкт, який є клоном даного об'єкта. </returns></summary>
        new T Clone();
    }

    // Тут все добре
    public abstract class CloneableClass : ICloneable<CloneableClass> // Тут тип-параметр правильний
    {
        /// <summary>
        /// Реалізує <see cref="MyNameSpace.ICloneable`1.Clone()"/>,
        /// в даному випадку "MyNameSpace.ICloneable&lt;CloneableClass&gt;.Clone()"
        /// </summary>
        public virtual CloneableClass Clone()
        {
            return (CloneableClass)MemberwiseClone(); // Ok
        }
        /// <summary> Реалізує <see cref="System.ICloneable.Clone()"/> </summary>
        object System.ICloneable.Clone() { return Clone(); } // Ok
    }

    // Хочу, щоб компілятор казав, що неможна передавати CloneableClass в даному випадку, але
    public class CrackCloneable : ICloneable<CloneableClass> // тут компілятор каже "все Ok"
    {
        public virtual CloneableClass Clone()
        {
            // throw new System.InvalidCastException();
            return (CloneableClass)MemberwiseClone();
        }
        // throw new System.InvalidCastException();
        object System.ICloneable.Clone() { return Clone(); }
    }
}

Як описати ICloneable<...>, щоб інтерфейси, класи і структури (нащадки ICloneable<...>) могли передавати як тип-параметр тільки себе, або своїх нащадків ?
Якщо це неможливо, то чому ?
І взагалі цікаво почитати ваші міркування щодо цього.

2 Востаннє редагувалося leofun01 (10.11.2015 12:09:57)

Re: System.ICloneable vs ICloneable<T>. Клонування об'єктів в C#.

В пошуках досконалого ICloneable'а, я дійшов висновку, що в C# його неможливо зробити таким як я хочу (принаймні покищо).
Рішенням цієї проблеми могло би стати використання ключових слів як типів у наслідуванні.
Наприклад код міг би виглядати так:

public interface ICloneable<out T> : System.ICloneable
    where T : ICloneable<T>, this
{
    new T Clone();
}

або так:

public interface ICloneable : System.ICloneable
{
    new this Clone();
}

Але C#-компілятор зригує кров'ю, коли я пишу таке збочення.

3

Re: System.ICloneable vs ICloneable<T>. Клонування об'єктів в C#.

Минуло 8 років. C# змінив версію кілька разів. Я знайшов спосіб правильно робити клонів. І я не розумію чому в самому фреймворку .NET цього не є.

Ось приклад коду:

using System;

namespace CloneableDemo {
    public static class CloneableExtension {
        /// <summary>Creates and returns <returns>the copy of the <paramref name="instance"/>.</returns></summary>
        /// <typeparam name="T">Type of this instance.</typeparam>
        /// <param name="instance">Cloneable instance.</param>
        public static T Clone<T>(this T instance)
            where T : ICloneable
        {
            return (T)instance?.Clone();
        }
    }
    public class SomeClass : ICloneable {
        object ICloneable.Clone() {
            SomeClass clone = (SomeClass)MemberwiseClone();
            // init clone fields
            return clone;
        }
        // // Optional public method also can be used.
        // public SomeClass Clone() { return this.Clone<SomeClass>(); }
    }
    public class OtherClass : ICloneable {
        public object Clone() {
            return (OtherClass)MemberwiseClone();
        }
        public static implicit operator SomeClass(OtherClass obj) {
            return new SomeClass();
        }
    }
    public static class CloneableTest {
        public static void Test() {
            SomeClass o = new SomeClass();
            object c0 = ((ICloneable)o).Clone();  // Ok : Implemented ICloneable method call.
            SomeClass c1 = o.Clone();             // Ok : Implicit extension method call.
            SomeClass c2 = o.Clone<SomeClass>();  // Ok : Explicit extension method call.
        //  SomeClass c3 = o.Clone<OtherClass>(); // Compile time error here is expected.
        }
    }
}

CloneableExtension є класом розширеня і містить метод розширеня.
Якщо ваш файл імпортує такий клас розширеня, то метод Clone() доступний для виклику з будь-яким екземпляром, який реалізує System.ICloneable.

Приклад імпорту такого класу розширеня:

using CloneableDemo;
// or
using static CloneableDemo.CloneableExtension;

// your code ...

SomeClass - приклад класу, який реалізує System.ICloneable. Для екземплярів цього класу будемо викликати методи Clone().

OtherClass - приклад класу, яким ми намагаємось зіпсувати (пошкодити) логіку роботи метода Clone().

CloneableTest.Test() - приклад перевірки роботи метода Clone().

Для реального проджекта достатньо копіювати static class CloneableExtension (весь клас).

4

Re: System.ICloneable vs ICloneable<T>. Клонування об'єктів в C#.

Ви молодці що рішили закрити тему, може кому згодиться (а форуму вцілому точно).
Я С# не знаю, але якось все рівно важко все це виглядає, чи може ви щось серйозніше хитріше(що я не розумію) робите.
В теорії все якось просто(сам я вивчаю Java) : якщо хочу зробити клон обєкту - створюю метод який повертає новий обєкт його ж класу (нам потрібна нова адреса) попередньо заповнивши його стан/поля/властивості з клонованого. Метод назву наприклад clone(). Якщо властивості не примітиви а обєкти(у них свої адреси) то роблю і їх клон (глибоке клонування), і щоб їх не реалізовувати сподіваюсь що хтось такий метод вже реалізував і він також називається clone(), і щоб все було "за контрактом" для всіх - імплементуємо інтерфейс з цим самим методом clone().
Це схоже на патерн прототип Прототип С#, які я на жаль досі "мучаю"...

Подякували: leofun011

5

Re: System.ICloneable vs ICloneable<T>. Клонування об'єктів в C#.

Ця задача більше про типи ніж про спосіб клонування. Основна ідея: obj.Clone() має повернути обєкт того ж типу що obj, тобто typeof(obj.Clone()) == typeof(obj). Сам System.ICloneable взагалі не сприяє такій поведінці, його метод повертає object, і це проблема.

До речі, от я й знайшов перший конфуз в моєму прикладі:

object     c0 = ((ICloneable)o).Clone();     // Acceptable.
ICloneable c1 = ((ICloneable)o).Clone();     // Compile time error was unexpected.
ICloneable c2 = ((ICloneable)o).Clone<ICloneable>(); // Ok : Explicit extension method call.

Якщо знайду ще якісь проблемні місця, то додам в цей топік.