9
\$\begingroup\$

While working with DirectShow, I came across the need to easily recognize different known 'sets' of GUIDs. E.g.: There are different GUIDs to indicate the possible time formats: None, Byte, Field, Frame, MediaTime, Sample. Working with enums instead of GUIDs would be a lot more useful in my opinion.

I decided to write the following abstract wrapper class which allows linking GUIDs to enums. You need to extend from it with the enum you want to expose as a type parameter.

/// <summary>
/// An abstract class to represent a set of GUIDs as an enum.
/// </summary>
/// <typeparam name="T">An enum to use for the type to expose.</typeparam>
abstract class AbstractGuidEnum<T>
{
    /// <summary>
    /// The internal GUID.
    /// </summary>
    public Guid Guid
    {
        get; private set;
    }

    /// <summary>
    /// The enum type for this GUID.
    /// </summary>
    public T Type
    {
        get;
        private set;
    }

    /// <summary>
    /// Specifies whether the GUID is known as a specific type or not.
    /// </summary>
    public bool IsKnownType
    {
        get; private set;
    }

    /// <summary>
    /// List matching GUIDs with the enum types.
    /// </summary>
    private static Dictionary<Guid, T> m_typeList;


    /// <summary>
    /// Create a new GUID enum wrapping the given GUID.
    /// </summary>
    /// <param name="guid"></param>
    protected AbstractGuidEnum( Guid guid )
    {
        Guid = guid;
        if ( IsGuidKnownType( guid ) )
        {
            IsKnownType = true;
            Type = GetType( guid );
        }
        else
        {
            IsKnownType = false;
        }
    }

    /// <summary>
    /// Create a new GUID enum for the specified type.
    /// </summary>
    /// <param name="type"></param>
    protected AbstractGuidEnum( T type )
    {
        Type = type;
        Guid = GetGuid( type );
    }

    /// <summary>
    /// Call to make sure the list of types is initialized.
    /// </summary>
    private void InitializeTypes()
    {
        if ( m_typeList == null )
        {
            m_typeList = new Dictionary<Guid, T>();
            FillTypeList( m_typeList );
        }            
    }

    public bool IsGuidKnownType( Guid guid )
    {
        InitializeTypes();

        return m_typeList.ContainsKey( guid );
    }

    /// <summary>
    /// Returns the type for a given GUID.
    /// </summary>
    /// <param name="type">The GUID to get the type for.</param>
    /// <returns>The type for the given GUID.</returns>
    public T GetType( Guid type )
    {
        InitializeTypes();

        if ( !m_typeList.ContainsKey( type ) )
        {
            throw new ArgumentException("No type is defined for the given GUID.", "type");                
        }

        return m_typeList[ type ];
    }

    /// <summary>
    /// Return the guid for a given type.
    /// </summary>
    /// <param name="type">The Type to get the Guid for.</param>
    /// <returns>The Guid for the given type.</returns>
    /// <exception cref="InvalidCastException">
    ///     Thrown when no Guid exists for the given type.
    /// </exception>
    private Guid GetGuid( T type )
    {
        InitializeTypes();

        try
        {
            return m_typeList.Keys.First( guid => m_typeList[ guid ].Equals( type ) );
        }
        catch ( InvalidOperationException )
        {
            throw new InvalidCastException( "No Guid exists for the given type." );
        }   
    }

    public override bool Equals( object obj )
    {
        if ( !(obj is AbstractGuidEnum<T>) )
        {
            return false;
        }

        AbstractGuidEnum<T> guidObj = obj as AbstractGuidEnum<T>;
        return Guid.Equals( guidObj.Guid );
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return Guid.GetHashCode();
        }
    }

    /// <summary>
    /// Fill up the list which matches GUIDs to the desired enum types.
    /// </summary>
    /// <param name="typeList"></param>
    protected abstract void FillTypeList(Dictionary<Guid, T> typeList);
}

As an example, the time formats:

/// <summary>
/// The possible time formats for seeking operations.
/// </summary>
public enum TimeFormat
{
    None,
    Byte,
    Field,
    Frame,
    MediaTime,
    Sample
}

/// <summary>
/// A wrapper class which represents the possible media types for DirectShow.
/// Since a MediaType is a Guid, a simple enum couldn't be used.
/// </summary>
class GuidTimeFormat : AbstractGuidEnum<TimeFormat>
{
    /// <summary>
    /// Create a new GuidTimeFormat wrapping the given GUID.
    /// </summary>
    /// <param name="guid">The GUID to wrap.</param>
    public GuidTimeFormat( Guid guid ) : base( guid )
    {
    }

    public GuidTimeFormat( TimeFormat timeFormat ) : base( timeFormat )
    {            
    }


    protected override void FillTypeList( Dictionary<Guid, TimeFormat> typeList )
    {
        typeList.Add( DirectShowLib.TimeFormat.None, TimeFormat.None );
        typeList.Add( DirectShowLib.TimeFormat.Byte, TimeFormat.Byte );
        typeList.Add( DirectShowLib.TimeFormat.Field, TimeFormat.Field );
        typeList.Add( DirectShowLib.TimeFormat.Frame, TimeFormat.Frame );
        typeList.Add( DirectShowLib.TimeFormat.MediaTime, TimeFormat.MediaTime );
        typeList.Add( DirectShowLib.TimeFormat.Sample, TimeFormat.Sample );
    }
}

Plain and simple, but is this the cleanest approach? Any improvements, or reasons why this would be unnecessary?

E.g.: Notice that some candidate static functions can't be made static because of InitializeTypes which relies on the overridable FillTypeList method.


So far I agree with most of the comments made, I posted this code since I felt it might be 'inappropriately clever code'. One of the benefits, in a possible scenario is the following however:

// Add the found type to the list.
MediaType type = new MediaType
{
    MajorType = new GuidMediaMajorType(amType.majorType),
    SubType = new GuidMediaSubType(amType.subType)
};
break;

The types are 'wrapped' in the guid type, which tries to link them up to the correct enum, when found. The alternative would be to map unknown types to an 'Unknown' enum value, but then the guid information is lost. This does seem to be the only benefit of this wrapper over a simple helper class with conversion functions. By writing this code I actually managed to find a copy/paste bug in DirectShowLib, as duplicate GUIDs were declared. Ofcourse, I would have also found this using a simple dictionary approach.

\$\endgroup\$
  • \$\begingroup\$ Do you have to use Guid? I can imagine easier ways to map enum into Guid if you do not it to be truly random. \$\endgroup\$ – Snowbear Mar 21 '11 at 20:11
  • \$\begingroup\$ DirectShow uses GUIDs for all kinds of stuff, so yeah, I'm dependant on them. \$\endgroup\$ – Steven Jeuris Mar 21 '11 at 22:56
  • \$\begingroup\$ Are you going to use TimeFormat enum primarily in your code and GuidTimeFormat will be used only where you need to map enum into DirectShow guid? I have a strong feeling that this is made to complicated but until I get how are going to use it I can't fully express that feeling \$\endgroup\$ – Snowbear Mar 21 '11 at 23:24
  • \$\begingroup\$ The GUID wrappers are supposed to be used only in interop code, and passed around there and such, e.g. for easier debugging, as GUIDs themselves don't say much. They are used in multiple classes. The exposed API only exposes the enums. You're making me wonder whether simple statically initialized dictionaries aren't a better solution. Although that wouldn't support unknown types, e.g. for media types. The 'advantage' of this approach is that the wrapper class is used as an 'enum' value and passed as such. \$\endgroup\$ – Steven Jeuris Mar 22 '11 at 0:02
6
\$\begingroup\$

I would avoid casting the same thing more then once. You can simply use as, it will return null if the object is not of the correct type.

public override bool Equals( object obj )
{
    AbstractGuidEnum<T> guidObj = obj as AbstractGuidEnum<T>;
    if ( guidObj == null )
    {
        return false;
    }

    return Guid.Equals( guidObj.Guid );
}

If you are going to have an equals method, it might be good to implement IEquatable and IEquatable<AbstractGuidEnum>.

I don't see anywhere that AbstractGuidEnum.Type, Guid, and IsKnowType are set, other then the constructor. If that is the case, I would make them into properties with backing fields and make the backing fields readonly.

\$\endgroup\$
  • 1
    \$\begingroup\$ Very true on the equals, now it's actually a two liner, the as cast followed by: return guid != null && Guid.Equals( guid.Guid ); \$\endgroup\$ – Steven Jeuris Mar 22 '11 at 0:12
  • \$\begingroup\$ You are also right about the readonly, it's a pity there isn't a concise way to specify this with automated properties yet, like e.g. dropping the set altogether, or writing readonly set; \$\endgroup\$ – Steven Jeuris Mar 22 '11 at 0:31
3
\$\begingroup\$

I think your solution violates the Kiss_principle. What benifit does your solution have over a simple but DRY violating

public TimeFormat GetType( Guid type )
{
   switch (type) {
     case DirectShowLib.TimeFormat.None : return TimeFormat.None;
   ...


public Guid GetType( TimeFormat type )
{
   switch (type) {
     case TimeFormat.None  : return DirectShowLib.TimeFormat.None;
   ...

See also Seven Deadly Sins of Programming - Sin #6 - Inappropriately clever code

\$\endgroup\$
  • \$\begingroup\$ I don't like this exact solution, because you have to specify both directions of Enum <-> Guid mapping separately and in this manner it leaves a space for human errors. I would replace it with some run-time mapping via bidirectional dictionary. Though I partially agree that something made in too sophisticated way here. \$\endgroup\$ – Snowbear Mar 22 '11 at 11:05
  • 3
    \$\begingroup\$ A switch on Guid isn't possible. This most definitly calls for at least a dictionary. \$\endgroup\$ – Steven Jeuris Mar 22 '11 at 12:16
  • \$\begingroup\$ I updated with an example and explanation of one additional advantage over helper functions. \$\endgroup\$ – Steven Jeuris Mar 22 '11 at 12:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.