Сделайте объект равным другому, внутренне - PullRequest
0 голосов
/ 13 ноября 2018

Учитывая объект A, типа MyObj и объект B также того же типа, каков наилучший способ определить метод в MyObj, который будет принимать аргумент MyObj иустановить текущий объект равным этому.

Пример:

class MyObj 
{ 
    /*lots of fields*/
    public void SetEqual(MyObj other)
    {
        /* What should go here, so that when the method ends, this is equal to other in every field OR they are ReferenceEqual */
    }
}

В настоящее время я могу думать только о том, чтобы отразить или вручную установить каждое значение равным (не дружественным для рефактора) - нетспособ сделать текущий объект псевдонимом другого (по аналогии с ключевым словом ref) по понятным причинам получить ссылочное равенство, поэтому единственное значение - это равенство значений. Существует ли более эффективный и прагматичный способ сделать это (отражениемедленный, ручной неуклюжий) или нет?

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

Ну, на ум приходят 4 метода. (13.11.2008) Отредактировано: добавлен метод 4

Метод 1 :

Код для ручного извлечения каждого отдельного свойства из другого и сохранения его в текущем экземпляре объекта.

расквитаться:

  • (относительно) Быстро
  • Явный контроль над тем, какие свойства передаются (если вы хотите исключить некоторые свойства)
  • Явный контроль глубины и мелкости каждого свойства

Downsides:

  • Техническое обслуживание
  • Легко пропустить, когда будущий разработчик (может быть, вы, а может и нет) добавляет новое поле / свойство в класс

Это может выглядеть примерно так:

class MyObj 
{ 
    public void SetEquals(MyObj other)
    {
        if (object.ReferenceEquals(this, other)) return; // We are equal by reference, so do nothing.
        if (other == null) return; // Throw ArgumentException? Up to you.
        this.Property1 = other.Property1;
        this.Property2 = other.Property2;
        this.Property3 = other.Property3;
        // ...
    }
}

Метод 2 :

Напишите пользовательский класс-помощник отражения. Я бы, вероятно, сделал это статичным, используя несколько открытых статических методов, и внутренне сохранял отраженные типы и необходимые данные в словаре или чем-то, что было бы ключом Type, и содержал отраженную информацию, чтобы вы не повторяли отражение для одного и того же типа при каждом вызове. Это все равно будет дороже в вычислительном отношении при первом использовании его для любого данного типа, но впоследствии это будет быстрее, если вы используете какой-то кэш. Кроме того, вы можете посмотреть на создание пользовательского атрибута, который будет указывать вашему вспомогательному классу отражения для OverrrideObjectByValue для игнорирования определенных свойств / полей.

расквитаться

  • Почти не требует обслуживания
  • Может быть написано с атрибутами, которые вы украшаете свойства / поля чтобы направлять отражатель на работу

Downsides

  • Медленно (по крайней мере, для начального отражения, но если вы кешируете, это будет быстрее)
  • Сложно написать, если у вас мало или нет опыта с Reflection
  • Запись его для поддержки глубоких и мелких копий вложенных типов во вложенных типах может стать изначально рекурсивной проблемой со сложными системами атрибутов, обеспечивающими гранулярный контроль глубоких и мелких случаев

Вы могли бы сделать это примерно так ...

using System.Reflection;
public static class OverrideObjectValues
{
    private static Dictionary<Type, Tuple<PropertyInfo[], FieldInfo[]>> cachedLookup = new Dictionary<Type, Tuple<PropertyInfo[], FieldInfo[]>>

    // Copies fields and properties from the right object into the left object.
    // Could be extended to support attribute-level customization
    // guiding this reflector on properties/fields to ignore,
    // And whether to perform a deep or shallow copy of reference types
    // for instance properties of types left and right.
    public static void OverrideValues(object left, object right)
    {
        // They are equal by reference, we're done.
        // This also handles the case that both left and right are null.
        if (object.ReferenceEquals(left, right)) return;

        // One or the other is null; we can't do this.
        // Alternatively, throw an ArgumentException here?
        if (left == null || right == null) return;

        // The types mismatch; we can't do this.
        // Alternatively, throw an ArgumentException here?
        // Note: We could modify this to support the case where
        // Left or Right inherits from the other, but that becomes
        // more complex, and is beyond the scope of what
        // you're asking for.
        if (left.GetType() != right.GetType()) return;

        Type leftType = left.GetType();

        if (!cachedLookup.ContainsKey(leftType))
        {
            // Add type to cache
            cachedLookup.Add(leftType, new Tuple<PropertyInfo[], FieldInfo[]>(leftType.GetProperties(), leftType.GetFields()));
        }

        // Iterate around each property, and copy-by-value from right into left.
        // Do the same for each field, for the type we cached in the dictionary.
        // You can add support to exclude properties/fields which are decorated
        // with custom attributes. If you do support guiding by custom attributes,
        // I'd exclude these types in the lookup/cache step in the dictionary before this point.
        // You could even add support to differentiate between structs and classes,
        // and do deep / shallow copies accordingly...
    }
}

Метод 3 :

Если вы хотите переопределить экземпляр A из MyObject значениями для экземпляра B из MyObject, вы можете просто использовать оператор присваивания, чтобы буквально сделать их равными по ссылке. Имейте в виду: это означает, что это один и тот же экземпляр, что означает, что внесение изменений в A отражается в B, поскольку A и B являются одним и тем же объектом в памяти.

расквитаться

  • быстрый
  • Легче всего понять в будущем (при условии, что вы знаете, как работают ссылочные типы)
  • Техническое обслуживание

Downsides

  • Не глубокая копия

Это было бы так просто, как:

// Populate list of objects.
List<MyObj> objects = GetObjectsSomehow(); 

// Copy by reference object at index 4 over object at index 5.
objects[5] = objects[4];

Как вы хорошо знаете, этот пример для метода 3: , а не , глубокое копирование / перезапись исходных данных, а вместо этого создание двух (в этом случае я сохраняю их в списке) то же самое - по ссылке. Это особенно полезно, если объект является неизменным, поскольку это, как вы знаете, не нарушает весь принцип неизменности ...

Метод 4 :

(добавлен этот метод после комментария) Этот метод на самом деле просто синтаксический сахар, и его лучше всего оставить в качестве оператора присваивания, но если вам действительно нужен метод по какой-то причине, вы могли бы сделать это ...

расквитаться

  • Похоже, это то, что вы ищете?
  • То же (в основном), что и при использовании метода 3, установка равных по ссылке, использование оператора присваивания ...

Downsides

  • хак и ненужный подход при простом a = b; было бы достаточно ...

Некоторый произвольный пользовательский тип только с основными типами данных:

public class CustomType
{
    public string Name { get; set; }
    public int ID { get; set; }
}

И тогда у вас мог бы быть некоторый статический класс с методами расширения ...

public static class CopyUtilities
{
    public static void MakeReferenceEqual<T>(this T left, ref T right) where T : class
    {
        if (object.ReferenceEquals(left, right)) return; // we're reference-equal, so be done.
        right = left;
    }
}

, который вы могли бы затем использовать так:

CustomType a = new CustomType();
a.ID = 42;
a.Name = "Myself";

CustomType b = null;
a.MakeReferenceEqual(ref b);

// a.ID == b.ID
// a.Name == b.Name
// a == b, by reference.
0 голосов
/ 13 ноября 2018

Я думаю, вы неправильно понимаете, что на самом деле означает ссылка .

Две переменные называются равными ссылкам, когда они обе ссылаются на один и тот же объект (*). Два объекта не равны между собой, потому что это бессмысленно; если они являются двумя различными объектами, то по определению они никогда не могут быть равны.

Теперь, что может сделать ваш метод SetEqual, это сделать два разных объекта равными, когда равенство имеет семантику значений. Как выглядит семантика значений равенства для ваших конкретных типов, только вы можете знать.

(*) Обратите внимание, что согласно этому определению тип значения никогда не может ссылаться на что-либо равное, потому что переменные типа значения не хранят ссылку.

...