Анонимные внутренние классы как ключи в Java, но что в C #? - PullRequest
1 голос
/ 25 января 2011

В Wicket у них есть то, что называется MetaDataKey .Они используются для хранения типизированной метаинформации в компонентах Wicket.Так как Wicket интенсивно использует сериализацию, дизайнеры Wicket решили, что простая идентификация объекта не будет надежной, и поэтому сделали MetaDataKey абстрактным классом, заставив вас создать подкласс для каждого ключа, а затем проверить, является ли ключ экземпляром подкласса.(из исходного кода Wicket):

public boolean equals(Object obj) {
    return obj != null && getClass().isInstance(obj);
}

Таким образом, чтобы создать ключ и сохранить что-то, я бы сделал что-то вроде этого:

private final static MetaDataKey<String> foo = new MetaDataKey<String>() {};

...

component.setMetaData(foo, "bar");

Во-первых, зачем работать подтип?лучше, чем использование идентификатора объекта при сериализации?

Во-вторых, если бы я хотел создать аналогичное средство в C # (в котором отсутствуют анонимные внутренние классы), как бы я это сделал?

Ответы [ 2 ]

2 голосов
/ 25 января 2011

Проблема с сериализацией и идентификацией заключается в том, что процесс сериализации фактически создает клон исходного объекта.Большую часть времени

SomeThing x = ...;
ObjectOutputStream oos = ...;
oos.writeObject(x);

, за которым следует

ObjectInputStream ois = ...;
SomeThing y = (SomeThing)ois.readObject()

, не может и не применяет это x == y (хотя так и должно быть, что x.equals(y))

Если вы действительно хотите использовать идентичность, вам придется написать собственный код сериализации, который обеспечивает, что чтение экземпляра вашего класса из потока дает фактически тот же (как в синглтоне) экземпляр, который был написан.Это трудно понять правильно, и я думаю, что заставление разработчиков сделать это просто для того, чтобы объявить волшебный ключ, сделает API довольно сложным в использовании.

В настоящее время можно использовать enum s и полагаться на ВМ для принудительного использования одноэлементного символа.

enum MyMetaDataKey implements HyptheticalMetaDataKeyInterface {

    TITLE(String.class),
    WIDTH(Integer.class);

    private final Class<?> type;

    private MyMetaDataKey(Class<?> t) { type = t; }
    public Class<?> getType() { return type; }
}

Недостатком является то, что вы не можете объявить свой enum для наследования от общего базового класса (однако он может реализовывать интерфейсы), поэтому вам придется вручную кодировать весь код поддержки, которыйMetaDataKey может обеспечить вас снова и снова.В приведенном выше примере все это getType должно было быть предоставлено абстрактным базовым классом, но это было невозможно, потому что мы использовали enum.

Что касается второй части вопроса ... К сожалению,Я не чувствую себя достаточно опытным в C #, чтобы ответить на это (помимо уже упомянутого, используйте простое решение частного класса, уже приведенное в комментариях).

Тем не менее ... ( Edit ответить на вопросы, появившиеся в комментариях к ответу Бена) Одно из возможных решений - добиться чего-то похожего (в том смысле, что оно будет столь же полезным, как и решение Java):

[Serializable]
public class MetaDataKey<T> {

    private Guid uniqueId;
    private Type type;

    public MetaDataKey(Guid key, Type type) {
        this.uniqueId;
        this.type = type;
    }

    public override boolean Equals(object other) {
        return other is MetaDataKey && uniqueId == ((MetaDataKey)other).uniqueId;
    }
}

, что может бытьиспользуется как в

class MyStuff {
    private static MetaDataKey<String> key = new MetaDataKey<String>(new Guid(), typeof(String));
}

Пожалуйста, игнорируйте любые нарушения языка C #.Это слишком долго, так как я использовал его.

Это может выглядеть как правильное решение.Проблема, однако, заключается в инициализации постоянной key.Если это делается, как в примере выше, каждый раз, когда приложение запускается, новое значение Guid создается и используется в качестве идентификатора для значения метаданных MyStuff.Так, если, скажем, у вас есть некоторые сериализованные данные из предыдущего вызова программы (скажем, сохраненные в файле), у нее будут ключи с другим значением Guid, равным ключу метаданных MyStuff.По сути, после десериализации любой запрос

 String myData = magicDeserializedMetaDataMap.Get(MyStuff.key);

не будет выполнен - ​​просто потому, что направляющие отличаются.Таким образом, для того, чтобы пример работал, у вас должны быть постоянные предопределенные Guids:

 class MyStuff {
     private static Guid keyId = new Guid("{pre-define-xyz}");
     private static MetaDataKey<String> key = new MetaDataKey<String>(keyId, typeof(String));
 }

Теперь все работает так, как вам нужно, но бремя поддержания ключевых Guides обрушилось на вас.Я думаю, именно этого Java-решение пытается избежать с помощью этой милой анонимной уловки подкласса.

1 голос
/ 25 января 2011

Что касается обоснования для подтипа против использования ссылочной идентичности, я нахожусь в полной темноте, как и вы. Если бы мне пришлось угадывать, я бы сказал, что поскольку ссылки - это в основном причудливые указатели, их значение не обязательно будет сохраняться при сериализации / десериализации, и умный способ симулировать уникальный идентификатор объекта - это то, что компилятор Java выберет называть определенный анонимный подкласс. Я не слишком знаком со спецификацией Java, поэтому не знаю, является ли это заданным поведением или подробностями реализации различных компиляторов. Я могу быть совершенно не в курсе.

C # имеет анонимные типы, но они строго ограничены - в частности, они не могут наследовать от чего-либо другого, кроме System.Object, и не могут реализовывать интерфейсы. Учитывая это, я не понимаю, как эту конкретную технику можно перенести из Java.

Один из способов сохранения уникальности объекта с помощью сериализации мог бы быть таким: базовый класс в стиле Wicket, теоретически, может сохранять закрытый член, который уникален во время выполнения и генерируется при построении, например System.Guid или System.IntPtr, который получает значение дескриптора объекта. Это значение может быть (де) сериализовано и использовано в качестве замены для равенства ссылок.

[Serializable]
public class MetaDataKey<T>
{
    private Guid id;

    public MetaDataKey(...)
    {
        this.id = Guid.NewGuid();
        ....
    }

    public override bool Equals(object obj)
    {
        var that = obj as MetaDataKey<T>;
        return that != null && this.id == that.id;
    }
}

EDIT Вот как это сделать, сохранив реальное значение объекта; это занимает немного меньше памяти и немного больше соответствует понятию равенства ссылок.

using System.Runtime.InteropServices;

[Serializable]
public class AltDataKey<T>
{
    private long id;  // IntPtr is either 32- or 64-bit, so to be safe store as a long.

    public AltDataKey(...)
    {
        var handle = GCHandle.Alloc(this);
        var ptr = GCHandle.ToIntPtr(handle);

        id = (long)ptr;

        handle.Free();
    }

    // as above
}
...