Fog Creek Software
Discussion Board




Welcome! and rules

Joel on Software

ICustomTypeDescriptor.GetProperties

I want to implement a class derived from ICustomTypeDescriptor.

My current implementation of the overloaded GetProperties methods look like this:

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(m_thing_with_properties,attributes,true);
        return hack_property_display_names(properties);
    }

    PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties()
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(m_thing_with_properties,true);
        return hack_property_display_names(properties);
    }

where "hack_property_display_names" is a private method that alters the
PropertyDescriptorCollection.

My problem with this implementation is that I'm not cacheing/controlling the PropertyDescriptorCollection. I would like to:

*    Construct my PropertyDescriptorCollection in my constructor and save it as private member data
*    Change the GetProperties() methods to return thhis private data.

My problem with doing this is that I don't understand the "Attribute[] attributes" parameter. The help says only that it acts as a filter. If I have a PropertyDescriptorCollection, what does it mean to (or, how do I) apply a "Attribute[] attributes" filter to it?

I haven't found this information on the 'net. The Mono and Rotor source codes don't include implementations of this. And the various samples programs that I find all seem to ignore the attributes filter.

Christopher Wells
Saturday, June 05, 2004

In case you're interested in this topic, I sent the following as email via the "Send feedback about this documentation" link for this method (and today received a reply that they've opened a "doc bug" regarding usage to be addressed in a future version of the documentation).

------------------

If more than one attribute is specified in the "Attribute[] attributes" parameter, it isn't clear whether the filter is looking for properties which have *any* of the specified attributes, or looking for properties which have *all* of the specified attributes.

I would appreciate an example of how this method *should* be implemented in a derived class: it seems to be non-trivial. My problem/uncertainty is compounded by the behaviour of PropertyDescriptor.Attributes.Contains in the presence of default attributes.

In a post on the microsoft.public.dotnet.framework.windowsforms I made the following guess, which could be wrong.

------

If I get "property.Attributes" for a typical property and look the resulting AttributesCollection using the debugger QuickWatch, then its Count=2 and its "attributes" data member contains only two attributes (CLSCompliantAttribute and ReadOnlyAttribute). If I then call the AttributesCollection.Contains method, to see whether it contains a BrowsableAttribute, then the Contains method returns true! This may be because BrowsableAttribute is contained as one of the attributes in the undocumented defaultAttributes member of AttributesCollection. If I create a new custom attribute, it doesn't seem to be added to the undocumented defaultAttributes member of the AttributesCollection returned by property.Attributes for all properties. Therefore, it seems to me that the correct implementation is to not use the apparently-unreliable AttributesCollection.Contains at all, but instead to test each "public" attribute in the collection, for example as follows:

public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
  PropertyDescriptorCollection returned_properties = new PropertyDescriptorCollection(new PropertyDescriptor[0]);
  foreach (PropertyDescriptor property in m_properties)
  {
  AttributeCollection property_attributes = property.Attributes;
  if (property_attributes_match_attributes_filter(attributes,property_attributes))
  {
    returned_properties.Add(property);
  }
  }
  return returned_properties;
}

static bool property_attributes_match_attributes_filter(Attribute[] attributes_filter,AttributeCollection property_attributes)
{
  //see whether any attribute in the attributes_filter match an attribute in the property_attributes collection
  bool b_attribute_specified = false;
  foreach (Attribute attribute in attributes_filter)
  {
  //we cannot use property_attributes.Contains because if Contains returns false
  //we don't know whether that's because the attribute was specified but with the
  //wrong value, or whether it's because it wasn't specified at all (not even
  //specified in the undocumented property_attributes.defaultAttributes member).
  // if (property_attributes.Contains(attribute))
  //  return true;

  //Therefore instead test each public (non-default) property attribute individually.
  foreach (Attribute property_attribute in property_attributes)
  {
    if (property_attribute.TypeId == attribute.TypeId)
    b_attribute_specified = true;
    if (property_attribute.Match(attribute))
    return true;
  }
  }
  if (b_attribute_specified)
  {
  //an attribute was specified but didn't Match, therefore return false
  return false;
  }
  //else attribute wasn't specified in the property, so return true if the specified
  //attribute is the default value for this type of attribute
  foreach (Attribute attribute in attributes_filter)
  {
  if (attribute.IsDefaultAttribute())
    return true;
  }
  //not specified and not default
  return false;
}

Christopher Wells
Wednesday, June 09, 2004

*  Recent Topics

*  Fog Creek Home