Iterating DesignerSerializationVisibility.Content collections?
Running the following code to get the property descriptor of ToolStripContainer.ContentPanel results in a descriptor marked with DesignerSerializationVisibility.Content.
ToolStripContainer tsc =newToolStripContainer();
PropertyDescriptorCollection props =TypeDescriptor.GetProperties(tsc);
PropertyDescriptor prop = props["ContentPanel"];Up until now, I've been using IEnumerable to iterate over objects which are marked DesignerSerializationVisibility.Content but "ContentPanel" isn't IEnumerable and prop.Converter reports that it cannot convert to IEnumerable. What am I missing? What is the appropriate way to iterate a DesignerSerializationVisibility.Content tagged property?
Thanks,
Ray
Looking at this again, I believe I'm misinterpreting DesignerSerializationVisibility.Content. I thought Content was used on collections only but that is not the case as ContentPanel illustrates.
- Ray
Hi, Ray,
Well, the only (main) difference between .Visible and .Content (as far as i see) is that in the second case the object will not be created by the deserializer. The typical case is with collections: they are created by the owner object itself (in ctor, for instance) and are never assigned.
The same can be done with any other object: (i) create it in ctor; (ii) publish the pointer with a getter; (iii) serialize/deserialize/assign individual properties, not the whole object. Loosely speaking this kind of sub-object can be seen as a part of the owner: it is born and dies together with the master.
This is a design decision. If a sub-object is created by the owner, it must be [de]serialized with .Content attr. If it is created by object users, it must have a setter to assign the pointer and must have .Visible attr. It is recommended to create collections from within the owner and not to assign them. Only collection members will be created by the object users.
It is quite possible to decorate collections with .Visible (the std Deserializer can not handle them propperly in this case; this faeture is not implemented, since assigning collections is a bad taste; true, it is). You could write a custom deserializer, but what for?
On the other side, decorating a sub-object accessor with .Content is absolutely legal. What you loose in this case (not by or not only by serializing, but when you use the object designed this way) is the abbility to assign an object of a derived class -- the owner creates the instance and you cannot control the creation.
Thus: A more flexible design decision prompts creating individual property-objects (as opposed to collections) externally (by the user-objects, not by the owner-object). And this implies serialization with .Visible, not with .Content. But then you must tell the Deserializer how to create that special object type (Convertor converting to InstanceDescriptor).
With .Content attr you need no convertors to deserialize sub-objects. If a property is simple and noone never needs using derived objects (Location, Size -- i canhardly imagine that i ever need to inherit from, even if it were possible), th .Content attribute can be better.
Regards,
Dima.
Thanks for the thoughtful response Dima. That clarifies my misunderstanding even more.
As far as .Content on simple properties, I'd assume that all value types (Location, Size) could be serialized as .Content independent of their being marked as such since assignment of the property is essentially a content assignment.
I'm still unclear on how .Content marked collections are handled. There must be some set of requirements placed on a .Content collection that the serializer uses in order to serialize/deserialize the items (note: this is in the context of writing my own serializer/deserializer using typedescriptors and designer serialization attributes).
How does the serializer determine the content is actually a collection? I currently look for the IEnumerable interface on the serialization side but I suspect there are ways in which this could fail.
Deserialization is even more obscure. I start by looking for an Item property from which I get the assumed base type of the collection. Then I look for an AddRange method that accepts the base type since some collections will fail to add individual items if they are dependent on other items not yet in the collection. If there is no AddRange method, I look for an Add method which accepts the base type. This seems pretty restrictive and fragile.
Is there a set of requirements on collections tagged .Content in order to have them serialize/deserialize properly?
Thanks again,
Ray
Hi, Ray,
(*) Structures. I try not using them. Someone wrote: "Think twice, do you really need them? And if yes, then dont use them." I forget always one critical thing. If S is a structure-valued property of object A and has a string field/property F. Is it enough to assing a new field value this way: A.S.F = "sdf"; Or this will change only the hidden intermediate (temporary) copy of the structure and even more -- result in a compiler error? And the intended effect can be reached with
(i) S x = A.S; x.F = "asdf"; A.S = x;
or something with
(ii) A.S = new S( ..., "ssd", ...);
A Content deserializing handles properties as in (i), a .Visible deserializing acts as in (ii) and needs a setter. Well, now i understand. Strcts are sealed and there is no benefit using not-Content serializing.
(**) Collections. Yes, serializing them is fragile, restrictive and nearly unnatural. But as long as you need no "special effects" it works. It may be usefull to follow in any new serializer exactly the rules used in the sdandard one. With Lutz Roeder's Reflector you can see these rules in all detail.
I wrote a serializer also. Your idea first to look for the item type inspecting .Item() is really good. May I steal?
Regards,
Dima.
Structures do have their use, especially when it comes to efficiency of collections of them but I tend to agree, they can create problems as you point out. However, you should also be aware that there is nothing stopping a setter or getter from making a copy of an object when set/get is called:
Prop property
{
set
{
this.prop = new Prop(value);
}
get
{
return new Prop(this.prop);
}
}
which implies that getting a reference to a property and setting values through that reference might not always work either. This set/get pair acts the same as a struct property even if Prop is a class. In both cases you must issue the statement prop = newValue to ensure the property receives the new values.
I had a look at Lutz's Reflector but I don't understand what you're refering to when you say that I would be able to see the rules the standard serializer uses.
- Ray
An interesting trick. Have never thought of something like that. But the fieeling is mixed: Is this the best way to cheat oneself?
To Reflector. I mean the following.
You may need to add System.Design.dll to the list of open files. The dll is in ...Windows\Microsoft.NET\Framework\v.1....\.
Then go to
{}System.ComponentModel.Design.Serialization
CollectionCodeDomSerializer
Serialize().
In the right panel (text) you'll find some interesting details. For instance:
if( typeof( Array).IsAssignableFrom( <given type>))
SerializeArray( ...);
and all checks for Add/AddRange. There is a flag PreferAddRange which is always true (it is implemented as a getter returning constant "true"). Seams like the developer was not sure and has left himself a possibility to prefer Add without changing the code.
Sometimes the Reflector is the only and the last resource.
Dima.