1

In Wicket, they have something called a MetaDataKey. These are used to store typed meta information in Wicket components. Since Wicket makes heavy use of serialization, the Wicket designers decided that simple object identity would not be reliable and so made MetaDataKey an abstract class, forcing you to create a subclass for each key and then check to see if the key is an instance of subclass (from the Wicket source code):

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

Thus, to create a key and store something I would do something like this:

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

...

component.setMetaData(foo, "bar");

First, why would making a subtype work better than using object identity under serialization?

Second, if I wanted to create a similar facility in C# (which lacks anonymous inner classes), how would I go about it?

cdmckay
  • 31,832
  • 25
  • 83
  • 114

2 Answers2

2

The problem with serialization and identity is, that the serialization process actually creates a clone of the original object. Most of the time,

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

followed by

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

cannot and does not enforce that x == y (though it should be the case, that x.equals(y))

If you really wanted to go with identity, you'd have to write custom serialization code, which enforces, that reading an instance of your class from the stream yields actually the same (as in singleton) instance that was written. This is hard to get right, and I think, forcing developers to do that simply to declare a magic key would make the API quite hard to use.

Nowadays, one could use enums, and rely on the VM to enforce the singleton character.

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; }
}

The disadvantage is, that you cannot declare you enum to inherit from the common base class (you can have it implement interfaces, though), so you would have to manually code the entire support code, which MetaDataKey might provide for you, over and over again. In the example above, all this getType should have been provided by an abstract base class, but it couldn't because we used enum.

As to the second part of the question... Unfortunately, I don't feel proficient enough in C# to answer that (besides the already mentioned use a plain private class solution already given in the comments).

That said... (Edit to answer the questions which appeared in the comments on Ben's answer) One possible solution to achieve something similar (in the sense, that it would be just as usable as the Java solution):

[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;
    }
}

which may be used as in

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

Please ignore any violations of the C#-language. It's too long since I used it.

This may look like a valid solution. The problem, however, lies in initialization of the key constant. If it is done like in the example above, each time, the application is started, a new Guid value is created and used as the identifier for MyStuff's meta-data value. So if, say, you have some serialized data from a previous invocation of the program (say, stored in a file), it will have keys with a different Guid value of MyStuff's meta-data key. An effectively, after deserialization, any request

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

will fail -- simply because the Guids differ. So, in order to make the example work, you have to have persistent pre-defined Guids:

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

Now, things work as desired, but the burden of maintaining the key Guids has come upon you. This is, I think, what the Java solution tries to avoid with this cute anonymous subclass trick.

Dirk
  • 30,623
  • 8
  • 82
  • 102
1

As far as the rationale for subtyping vs using reference identity, I'm as much in the dark as you. If I had to guess, I'd say that as references are basically fancy pointers, their value isn't necessarily guaranteed to be retained through serialization/deserialization, and a clever way to simulate a unique object ID is with whatever the Java compiler chooses to call a particular anonymous subclass. I'm not too conversant with the Java spec, so I don't know if this is specified behavior or an implementation detail of the various compilers. I may be entirely off track here.

C# has anonymous types, but they are severely limited - in particular, they cannot inherit from anything other than System.Object and can implement no interfaces. Given that, I don't see how this particular technique can be ported from Java.

One way to go about retaining object uniqueness through serialization could go thusly: The Wicket-style base class could, in theory, retain a private member that is runtime-unique and generated on construction, like a System.Guid or a System.IntPtr that gets the value of the object's handle. This value could be (de)serialized and used as a stand-in for reference equality.

[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 Here's how to do it by saving the object's actual reference value; this takes a bit less memory, and is slightly more true to the notion of reference-equality.

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
}
Ben
  • 6,023
  • 1
  • 25
  • 40
  • Cool, this is what I was thinking. I think this is actually a lot nicer as it doesn't require the user to do anything wacky like create a subclass to make a key. – cdmckay Jan 25 '11 at 01:46
  • Hm. This could almost work, but you should not generate new GUIDs in the constructor (essentially, serialized and then deserialized instances of the class would not be equal to the "master key" across process life-time). However, just passing the GUID into the constructor would be sufficient to make your solution work, I think. – Dirk Jan 25 '11 at 01:54
  • Thinking about it again... Unless you have a fixed constant GUID value, I don't think, that this scheme would survive a restart of the process at all. – Dirk Jan 25 '11 at 02:00
  • Dirk, I don't think that the constructor gets called by the BinaryFormatter on deserialization, so the Guid should only be assigned once. Actually, I've never before heard that one should avoid generating a Guid in a constructor - can you elaborate on that point? – Ben Jan 25 '11 at 02:38
  • @Ben: It's not so much the constructor call I am worried about. But you need to have a stable GUID which survives process boundaries. If you have a unified `MetaDataKey` class, the only property, which can be used to discriminate between different keys (apart from the associated value type) is the GUID. If you generate a new master key GUID each time, the application is started, you cannot restore data properly, which was serialized by a prior invocation of the application (the values have been stored using keys with a different GUID) – Dirk Jan 25 '11 at 10:40
  • @Dirk: Would the subclass approach not suffer from that drawback? – cdmckay Jan 25 '11 at 15:02
  • @Dirk: I believe I may be misunderstanding you; here's what I perceive: This guid would be created once, on key creation. When the key is serialized, the value of the guid will be included. On deserialization, the original value will be assigned to the cloned instance - even if the constructor was called (which, according to Reflector, it isn't), the new guid would be overwritten by the original value. – Ben Jan 25 '11 at 19:02
  • @Ben, sometimes, SO seems not to be the right medium... I'll augment my answer. – Dirk Jan 25 '11 at 20:39