http://blogs.clariusconsulting.net/kzu

Daniel Cazzulino's Blog

Go Back to
kzu′s Latest post

How to hide System.Object members from your interfaces: the IFluentInterface

Sometimes, System.Object methods (Equals, GetHashCode, GetType and ToString) only contribute clutter to VS intellisense. Everyone knows those members are always there, yet they are seldom used explicitly. This is especially important (and annoying) for fluent APIs that define the flow of invocations in terms of interfaces and usually have few members at each "step" of the statement.

For example, in the following Moq expectation, at the particular step in the statement, there is only one "real" invocation that makes sense (Verifiable). However, it is obscured by the System.Object members:

image

A much cleaner intellisense is possible though:

image

The trick comes from the System.ComponentModel.EditorBrowsableAttribute, which controls visibility of members in VS intellisense. To hide a member from intellisense, you apply the following attribute to it:

[EditorBrowsable(EditorBrowsableState.Never)]

Now, you don’t want to have to override all four object members in every type just to apply the attribute. A quite elegant solution exists, which involves taking advantage of implicit interface implementation. In particular, you can define an interface that re-defines all object members and applies the attribute:

/// <summary>
/// Interface that is used to build fluent interfaces and hides methods declared by <see cref="object"/> from IntelliSense.
/// </summary>
/// <remarks>
/// Code that consumes implementations of this interface should expect one of two things:
/// <list type = "number">
///   <item>When referencing the interface from within the same solution (project reference), you will still see the methods this interface is meant to hide.</item>
///   <item>When referencing the interface through the compiled output assembly (external reference), the standard Object methods will be hidden as intended.</item>
/// </list>
/// See http://bit.ly/ifluentinterface for more information.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IFluentInterface
{
    /// <summary>
    /// Redeclaration that hides the <see cref="object.GetType()"/> method from IntelliSense.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    Type GetType();

    /// <summary>
    /// Redeclaration that hides the <see cref="object.GetHashCode()"/> method from IntelliSense.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    int GetHashCode();

    /// <summary>
    /// Redeclaration that hides the <see cref="object.ToString()"/> method from IntelliSense.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    string ToString();

    /// <summary>
    /// Redeclaration that hides the <see cref="object.Equals(object)"/> method from IntelliSense.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    bool Equals(object obj);
}

Now you simply add this interface to all your classes or interfaces where you want to hide these members. Starting in Moq v2, we’ve done this with all the interfaces in our fluent API so that they don’t clutter your discovery of the expected flow:

public interface IVerifies : IFluentInterface

Update: a whole lot of projects are leveraging this technique now :) , pretty much all that have a fluent API. Over time, I’ve started liking the IFluentInterface name better than my original IHideObjectMembers, but I’ve seen others using IFluentSyntax too. If you do leverage this technique, I’d appreciate it if you link to http://bit.ly/ifluentinterface in your code documentation Smile. Thanks!

Update2: to grab this interface declaration, and stay up to date with documentation updates, just install the IFluentInterface nuget!

 

 

Sometimes I do love VS :)

Comments

18 Comments

  1. Damn, i’ve never think about it.
    Thanks.

  2. Eminently useful. I never thought of this, but it makes absolutely perfect sense. Thanks!

  3. Doesn’t seem to work with Resharper :(

  4. Thanks Bud!! You Rock! Very Cool! I Love this solution. This bit of object craziness has always driven me spare :) Thanks Again!

  5. It’s funny how you use those fluent interfaces all days long and never realize “Hey… where did the System.Object members go?”. Can’t wait to fix my fluent api at work on Monday, awesome post!

  6. Its not a resharper bug, you just need to configure resharper to respect the attribute: In Resharper Options, go to Environment | IntelliSense | Completion Appearance and check “Filter members by [EditorBrowsable] attribute”.

  7. This trick can have bad consequences for F# users. F# does not support implicit interface implementation so we have to implement all of the IHideObjectMembers members explicitly. Probably OK in a fluent API, but I’ve seen this leak into framework interfaces. Then it’s a pain. Don’t forget about us F#’ers! :)

  8. :)

    It’s an interesting topic. Have a read of this and see what you think http://cs.hubfs.net/topic/None/57913#comment-66094

    It is fine for an object to implement IFluentInterface or IHideObjectMembers but it seems wrong when inherited by a domain interface. Here is the example that triggered my comment:
    https://github.com/NancyFx/Nancy/blob/master/src/Nancy/IRootPathProvider.cs#L8

    Thoughts?

  9. I agree its usage in Nancy is very weird. It was intended as a means to hide those members only in fluent APIs. I still don’t understand how it is relevant or annoying for F# code consuming those APIs though…

  10. I should have been clearer. It’s not a problem when _consuming_ fluent builders. It is ugly when used as it has been with Nancy (i.e. interface inheritance) and you want to create and implementation of IRootPathProvider in F#. We have to implement all the members like this https://github.com/bentayloruk/cantos/blob/dev/Cantos/Nancy.fs#L18

  11. Wouldn’t it be more useful if the NuGet package added a reference to a compiled assembly rather than adding a .cs file? The hiding doesn’t take effect if the cs file is in the same open solution.

    • I don’t think so, because:
      1 – You wouldn’t want to take a dependency on a binary for just a single file with an interface and 4 members.
      2 – The effect of not hiding on the same solution would still remain
      3 – Your assembly user don’t have your source in the same solution, so the “problem” is moot.

  12. Thanks for the reply. I thought I could declare IMySyntax : IFluentInterface and the hiding would work so long as IFluentInterface was in an external assembly. I now see this is not the case and IMySyntax would have to be in the external assembly as well.