Make everything public. Mark Two

So a while back I wrote a post Make everything public which proposed making your entire API public.

Just to recap it proposed that instead of using permission modifiers (internal and private) we make everything public and instead use [EditorBrowsable(EditorBrowsableState.Advanced)] to hide members.

For the purposes of this discussion the two approaches will be referred to as the modifier approach and the attribute approach.

The Score Board

First off I would like to go through the various trade-offs of the two approaches as it stood when i wrote the initial post.

Performance

Using the attribute approach there is no need to use reflection when accessing hidden members. This results in a significant performance improvement over the modifier approach.

Outcome: Win for the attribute approach

Code Usability and Readability

With the attribute approach people can use strong typing and intellisense when writing their code. This is much less painful than the modifier approach which results in ugly reflection code.

Outcome: Win for the attribute approach

Visibility and Surface Area

The attribute approach hides things unless the user enables advance features in their editor. The modifier approach hides things unless you use reflection. Both approaches effectively hide types and members from the consumer of the API. So the visibility of things and the surface area of you API does not change.

Outcome: Draw

Encapsulation

While encapsulation is an important tenant of Object Orient programming is it not truly enforced in .net. With the modifier approach it can be bypassed with reflection. With the attribute approach it can be bypassed by turning on advanced features of the editor.

At best encapsulation in .net is an angry old man yelling "keep off my lawn".

Outcome: Draw

Don't use this part of my API

Many people avoid making things public so that is is easier to make changes in the future. The theory is that the smaller the surface area the smaller the agreed API contract is. So when you make breaking changes you only need to be careful about the public part of you API.

In reality people will use reflection to bypass that contract anyway.

So with the modifier approach you take the stance of "please don't use anything that is marked internal or private". With the attribute approach you take the stance "please don't use anything marked with [EditorBrowsable(EditorBrowsableState.Advanced)]". Not really much of a difference.

Outcome: Draw

Proper visibility within your project

Note: this point is referring to the code base of the API

Since the attribute approach makes no distinction between internal and private you can not property express all scenarios that you can with modifier approach.

Outcome: Win for modifier approach

Amount of code

The attribute approach requires the use of [EditorBrowsable(EditorBrowsableState.Advanced)]. This is clearly much more verbose than modifier approach which uses internal and private.

Outcome: Win for modifier approach

Outcome

If you sum up the points it is two each. However the last two points were always blocking issues for me when pitching this idea to people. To really convince people of a differnet idea you have to make the transition as smooth as possible. So I hade to make the last two points at least Draws. This way there would be no drawbacks of using the attribute approach.

Enter IL Weaving

I turned to IL Weaving in an attempt to solve the last two points and created the Publicize project. It is and addin to Fody which modifies an assembly just after it has been compiles.

Publicize performs the following actions

  • Find all types and members that are internal or private.
  • Convert these things to public.
  • Add [EditorBrowsable(EditorBrowsableState.Advanced)] to each of them.

So how does it solve that last two issues.

Proper visibility within your project

You can now use the internal and private modifies as you would normally. So all you rules about encapsulation and OO design will be followed in your code base.

Outcome: Draw

Amount of code

Since you don't need to write [EditorBrowsable(EditorBrowsableState.Advanced)] anywhere your code is no more verbose.

Outcome: Draw

In Summary

Hopefully some people got to this point without exclaiming "this guy is crazy" and closing the browser.

I think it is a pity that .net and c# were not initially designed with this type of approach. If it was build in to the language there could be a custom keyword similar to unsafe. Then everything inside it would bypass permission modifiers without the performance hit of reflection. Perhaps re-use the public keyword.

public
{
   //all code placed in hear will bypass permission modifiers
}

It is also a pity that people cling to the illusion that permission modifiers are respected.

I hope that the approach I have outlined above make people consider that there may be better approach the controlling the visibility of an API. The consumers of your API will love you for not forcing them to use reflection.

Posted by: Simon Cropp
Last revised: 08 Feb, 2013 11:11 PM History

Comments

05 Feb, 2012 05:38 PM @ version 3

Your main argument seems to be:

In reality people will use reflection to bypass that contract anyway.

This may be true in your projects, but this is clearly not true for the majority of devs. While I have used reflection to "hack" something, this is clearly a very rare thing and I'd never expect a library to behave normally once I have accessed an internal/private member.

When I create a private method I don't support it being used by someone else, so I can't see why I should care about the performance impacts for someone working around that...

Simon Cropp
Simon Cropp
06 Feb, 2012 10:29 AM @ version 3

@Martin

This isn't about how often you need to use reflection. To be honest I, like you, do not need to do much reflection into internal members. But that is because in my current job we have a free reign to pick the libraries and tool-kits we want. Since we pick mostly OSS if there is an issue with visibility we can usual get a patch accepted quickly.

However there is no argument that people often need to access internal members. Look at these two searches on StackOverflow.

  • http://stackoverflow.com/search?q=how+to+access+a+private+method+.net&submit=search
  • http://stackoverflow.com/search?q=how+to+access+a+private+property++.net&submit=search

A fair number of those are people asking how to bypass permission modifiers.

So given that is does happen I am proposing an idea that better caters for it when it does happen.

And thanks for the reply. I am surprised anyone got to the bottom of the post.

Martin
Martin
06 Feb, 2012 08:15 PM @ version 3

First of all, I never know such an attribute existed, so I have to thank as I learned something :)

But, back on topic, I think the original problem (not having influence on a library you have to use) can not be solved by something that says how that library should have been done in the first place :)

But still an interesting read. But I think a more realistic approach is to create some wrapper that caches the fields and methods you retrieve via reflection. This more or less eliminates all the performance issues. (At least in my experiments it did)

19 Apr, 2012 10:21 AM @ version 3

Making everything public isn't the answer to bad API and/or encapsulation. Fixing the API / encapsulation is the answer.

Making things public is a contract where the API developer is stating that they support this. They support in terms of versioning, back compatibility, documentation, etc. Proper encapsulation allow fixes, refactoring, performance improvements, etc without breaking consumers.

By-passing that contract via reflection puts the onus on you to support that code. This is a very important distinction. Just because encapsulation isn't strictly enforced by .NET, it clearly sets your expectations. The same expectations apply if one uses your Publize project (how does that handle debug symbols?), CECIL, or any other ilrewriting of a 3rd party assembly. By taking it and changing it, you are taking ownership, including supporting it.

Semver is based on public API and Everything Public means every single change is a breaking change.

Also, that EditorBrowsableAttribute assumes you are using Visual Studio to author, read and analyse APIs.

No new comments are allowed on this post.