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 enum
s, 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.