In Foof We Trust: A Dialogue

In Foof We Trust: A Dialogue

Rate This
  • Comments 53

User: The typeof(T) operator in C# essentially means “compiler, generate some code that gives me an object at runtime which represents the type you associate with the name T”. It would be nice to have similar operators that could take names of, say, methods and give you other metadata objects, like method infos, property infos, and so on. This would be a more pleasant syntax than passing ugly strings and types and binding flags to various Reflection APIs to get that information.

Eric: I agree, that would be a lovely sugar. This idea has been coming up during the C# design meeting for almost a decade now. We call the proposed operator “infoof”, since it would return arbitrary metadata info. We whimsically insist that it be pronounced “in-foof”, not “info-of”.

User: So implement it already, if you’ve been getting requests for the better part of ten years! What are you waiting for?

Eric: First, as I’ve discussed before on my blog, we have a very limited time and money budget for design, implementation, testing, documentation and maintenance, so we want to make sure we’re spending it on the highest-value features. This one has never made it over that bar.

Second, the feature seems at first glance to be simple, but in fact has a rather large “design surface” and would require adding a fair amount of new syntax to the language.

Third, it is nothing that you cannot do already with existing code; it’s not necessary, it’s just nice.

All these are enough “points against” the feature that it’s never made it into positive territory. It will stay on the list for hypothetical future versions of the language, but I would not expect it to ever happen.

User: Can you elaborate on some of the problems?

Eric: Let’s start with a simple one. Suppose you have infoof(Bar) where Bar is an overloaded method. Suppose there is a specific Bar that you want to get info for. This is an overload resolution problem. How do you tell the overload resolution algorithm which one to pick? The existing overload resolution algorithm requires either argument expressions or, at the very least, argument types.

Expressions seem problematic. Either they are evaluated unnecessarily, perhaps producing unwanted side effects, or you have a bunch of expressions in code that looks like a function call but isn’t actually. Either way is potentially confusing. So we’d want to stick with types.

User: Well then, it ought to be straightforward then. infoof(Bar(int, int)), done.

Eric: I notice that you’ve just introduced new syntax; nowhere in C# previously did we have a parenthesized, comma-separated list of types. But that’s not at all hard to parse.

User: Exactly. So go with that.

Eric: OK smart guy, what about this strangely familiar case?

class C<T>
{
  public void Bar(int x, T y) {}
  public void Bar(T x, int y) {}
  public void Bar(int x, int y) {}
}
class D : C<int>
{ …

You have some code in D that says infoof(Bar(int, int)). There are three candidates. Which do you pick?

User: Well, which would overload resolution pick if you called Bar(10, 10) ? Overload resolution tiebreaker rules say to pick the non-generic version when faced with this awful situation.

Eric: Great. Except that’s not the one I wanted. I wanted the method info of the second version. If your proposed syntax picks one of them then you need to give me a syntax to pick the others.

User: Hmm. It's hard because overload resolution actually gives you no way to force the call to go to one of the two “generic param” methods in this bizarre case, so some mechanism stronger than overload resolution needs to be invented if you want the feature to allow getting info of any method.

But this is a deliberately contrived corner case; just cut it. You don’t have to support this scenario.

Eric: And now you start to see how this always goes in the design meeting! It is very easy to design a feature that hits what initially looks like 80% of the cases. And then you discover that the other 80% of the cases are not covered, and the feature either takes 160% of its budget, or you end up with a confusing, inconsistent and weak feature, or, worse, both.

And how do we know what scenarios to cut? We have to design a feature that does something in every possible case, even if that something is an error. So we have to at least spend the time to consider every possible case during the design. There are a lot of weird cases to consider!

User: What are some of the other weird cases?

Eric: Just off the top of my head, here are a few. (1) How do you unambiguously specify that you want a method info of an specific explicit interface implementation? (2) What if overload resolution would have skipped a particular method because it is not accessible? It is legal to get method infos of methods that are not accessible; metadata is always public even if it describes private details. Should we make it impossible to get private metadata, making the feature weak, or should we make it possible, and make infoof use a subtly different overload resolution algorithm than the rest of C#?  (3) How do you specify that you want the info of, say, an indexer setter, or a property getter, or an event handler adder?

There are lots more goofy edge cases.

User: I’m sure that we could come up with a syntax for all of those cases.

Eric: Sure. None of these problems are unsolvable. But they almost all require careful design and new syntax, and we would soon end up spending most of our design, implementation and testing budget on this trivial “sugar” feature, budget that we could be spending on more valuable features that solve real problems.

User: Well, how about you do the easy 80% and make “the other 80%” into error cases? Sure, the smaller feature is weaker and might be inconsistent, but something is better than nothing, and it’ll be cheaper to just do the easy ones.

Eric: That doesn’t actually make it cheaper a lot of the time. Any time we make a corner case into an error we need to carefully specify exactly what the error case is so that we can implement it correctly and test it confidently. Every error case still spends design, implementation and test budget that could have been spent elsewhere.

We would need to come up with a sensible error message, localize the error message into I don’t know how many languages, implement the code that detects the weird scenarios and gives the right error, test the error cases and document them. And we would need to deal with all the users who push back on each error case and request that it work for their specific scenario.

Trying to scope the feature down so that it is smaller adds work, and it is possible that it adds more work in the long run than simply making the larger version of the feature work in the first place. There are no cheap features.

User: Bummer. Because most of the time I really just want to get the method info of the method I’m currently running, for logging purposes during my test suites. It seems a shame to have to solve all these overload resolution problems just to get information about what’s happening at runtime.

Eric: Maybe you should have said that in the first place! The aptly named GetCurrentMethod method does that.

User: So what you’re telling me is no infoof?

Eric: It’s an awesome feature that pretty much everyone involved in the design process wishes we could do, but there are good practical reasons why we choose not to. If there comes a day when designing it and implementing it is the best way we could spend our limited budget, we’ll do it. Until then, use Reflection.

  • Does your argument about "weak features" and having to document the error cases, not seem to apply neatly to the case of generic custom attributes? It is my understanding that the CLR understands custom attributes of generic types; only the C# compiler doesn't. It seems to me that generic custom attributes should have been implemented according to your argumentation, because you claim the amount of effort in implementing, documenting and maintaing the error case is comparable to that of implementing, documenting and maintaing the working feature. How many people regularly request this feature, and what are the difficulties and downsides of implementing it?

    Also, I notice one user has commented that they would like to be able to use infoof() in custom attributes. Since typeof() is allowed, this makes sense - it is in some sense a constant expression. But then why not simply allow expression trees and/or lambda expressions in custom attributes? They, too, are somewhat of a constant (in the sense that the compiler need not run any user code to evaluate it). Isn't this another case of a similar nature as the above, where the amount of effort of supporting the feature would be comparable to not supporting it because it is an error condition? How many users regularly request the ability to specify a lambda expression or expression tree in a custom attribute, and what are the difficulties and downsides of implementing it?

  • Why don't you just implement propertyof(Person.Age)? That should be easy to do and so much useful. If it is hard for methods, don't do it, 99% people want infoof for properties anyway!

  • I show on my blog how I use a now classic solution based on expression trees as an alternate solution: weblogs.asp.net/.../PropertyOf-INotifyPropertyChanged-PropertyChanged-strings-infoof.aspx

  • "Observation: I think the real reason people want an infoof operator is that they don't want to use reflection, even though what they're asking for is available via reflection. That seems to stem from reflection being, well, confusing to use, and a little unwieldy. "

    No.  It's because it's a RUN TIME mechanism, when what is really needed is a COMPILE TIME one.

  • "Given that, what is the real-world context most people want this stuff for? Enlighten me."

    The 99.9999% cases...

    INotifyPropertyChanging/Changed and simple data binding.

    And bottom line is that these functions are both needed,  and should be used by forms designers and other tools.

    Because their job should be to allow basic operations like data binding and change notification,  in simple, standardised ways that

    make refactoring easy

    don't leave string names for properties littering the code,  

    don't require application programmers to be doing things to fix what is a combined shortcoming in the language and libraries,  

    don't incur yet more un-necessary runtime overhead in reflection,  

    and that are proper language-level or core library level and therefore don't have maybe six or seven different implementations of the same functionality lying around just because different library authors have chosen to solve it in different ways.

  • I wonder if WPF dependency properties would have used infoof(Stuff) instead of a static "StuffProperty" had it been available. Settings like one-way vs two-way would have been neatly described in attributes and everything would have been rosy and gorgeous.

    What a shame we've missed that chance forever.

  • I don’t understand the relevance of accessibility. typeof() is already limited to accessible types. Surely infoof() should, too. Anything else would make no sense in the context of the existing language.

  • Eric, I was just noodling in my head about a hypothetical FxCop feature that could be used to emulate the "throws" functionality of Java.  (It was one of the things I missed moving from Java to C#):

    public void doSomething(String name) throws NullPointerException { ... }

    Since C# can't do that itself, I was considering a couple of ways to go about it.  For example, if the method could declare for itself:

    [Throws(typeof(ArgumentNullException))]

    public void DoSomething(string name) { ... }

    But there's no way to enforce [Throws] for library methods that you don't have access to.  So I thought of two solutions:

    - Static code analysis could probe all of the code paths, but this quickly has the potential to become enormously complex.

    - FxCop allows for certain attributes to be applied (in the 2005 and 2008 releases of Visual Studio with code analysis, I remember this being called GlobalSuppresions.cs

    Going down the path of the second idea, I thought it would be nice to have a 'methodof' operator:

    [assembly: LibraryThrows(methodof(System.IO.File.Open()), typeof(FileNotFoundException))]

    Or something to that effect....

    Instead, the best we could do is:

    [assembly: LibraryThrows(typeof(System.IO.File), "Open", new Type[0], typeof(FileNotFoundException))]

    Maybe not as neat.  And I realize it's certainly not one of those most-common-use-cases that you might otherwise see.  A fun thought experiment, nonetheless.

Page 4 of 4 (53 items) 1234