Things That Make You Go Hmmm
As you might have gathered from the number of times I make a blog post beginning with "I got the following question from a reader the other day..." I field a lot of questions about the C# language (and in the past fielded a lot of questions about VBScript and JScript.) Each of them is, by definition, about something that was unobvious about the language; if it were obvious, the question wouldn't have been asked in the first place.
This means that I always learn something when I answer questions. I learn what it is about this language that people find confusing, unobvious, tricky. These are useful data, because we can then use them to make better compiler warnings. We can use them to help us gauge early on what new language features people will find confusing. And so on.
Rather than wait for more questions to come in -- though believe me, plenty do -- I thought I'd take this opportunity to solicit stories of times when you learned something profoundly non-intuitive about programming languages. Preferably C#, but other languages would be interesting as well. If you've got a story to share, please leave a comment on the blog or send me an email. Thanks!
Comments
Anonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
You can mess around with the parameters to the base ctor: public Derived(int x) : base(x + 1) {} that's perfectly legal.Anonymous
January 21, 2009
My non-obvious story isn't as clear-cut because I don't recall the exact circumstances (this was in 2002), but it has to do with static methods/properties (I'll use "static method" throughout to refer to both). Basically, the first time I tried to put a static method in an interface, it failed, and the first time I tried to override a static method in a derived class, it failed. You had a post recently on why this is - it's because static methods merely are methods for which lookup can be made at compile-time, not class methods. This made me go not just "hmmm" but also something more vivid and unprintable than "hmmm", simply because of the two, I'd rather C# supported class methods. Class methods are very useful tools when it comes to really working with the hierarchy that object orientation in C# gives you; imagine inheriting from an abstract bitmap class and in the process overriding a class property, providing an easily way of checking, for example, what type or extension or color depth is supported by a specific image format. Sometimes class methods can be supplanted by attributes, but far from always -- it doesn't solve any of my two previously mentioned gotchas. In my seventh year of using C#, it still boggles my mind. I can see the case for static methods as far as executional expediency goes (no virtual dispatch), but I literally cannot see any other advantages over class methods. I recognize that class methods overtaking static methods at this point would be a compatibility nightmare and that I am likely out of luck, but if you entertain the question as if it was written and considered before C# 1 was ever public, what would your thoughts be on the subject?Anonymous
January 21, 2009
@Eric, Thanks that makes the point well. on its own:- public Derived(int x) : base(x) {} It didn't occur to me that I could do:- public Derived(int x) : base(fn(x)) {} I've coding in C# for some years and yet I still struggle to grok this. It could just be me being on the slow side but it just doesn't seem that obvious.Anonymous
January 21, 2009
@Anthony: I think the only reason C# has that syntax is because otherwise they'd have to invent a way of calling the initialization part of the constructor (what runs in the constructor and not the creation of the object itself) in regular code. Calling "new" creates the new object and then runs the appropriate constructor. It's not obvious, but it's also a special case, because you normally don't want to separate the two.Anonymous
January 21, 2009
I was surprised I could not constrain generic types with parameterized constructors. After much thought and discussion, I understand why that is the case (generic types describe an interface; construction is an implementation detail). Good call on that.Anonymous
January 21, 2009
I recall being very surprised when I've seen that arrays are covariant in .NET, in any position (I didn't come from a Java background, so I didn't realize where that misfeature was coming from, as it's certainly rather counter to common sense).Anonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
When I was first learning C, it took me a while to get to grips with pointers. The concept of a memory location that held the address of another memory location made perfect sense to me but the syntax confused me. In C, pointers are usually declared something like "int x;" suggesting that you're creating a variable whose type is "int" and whose name is "x". That's sort of what you're doing except not really at all - at this point you haven't created any variable of type "int". The C++ way of declaring pointers, "int x;" (suggesting you're creating a variable of type "int" whose name is "x") makes much more sense.Anonymous
January 21, 2009
Several years ago, after I'd been a VB programmer for a long time and had already moved to C#, I was surprised to see code using Mid() on the LHS, instead of the RHS. I had no idea a string could be modified in this way. [[ ERIC: We deliberately left that out of VBScript. It is deeply weird. ]]Anonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
The fact that using() with object initializers can cause a memory leak is certainly counter-intuitive.Anonymous
January 21, 2009
Re: best practices: I would say that C# does do a fairly good job of leading you towards the Pit of Success rather than the Pit of Despair, but of course we can always do better. Re: sealed: Yes, as I have stated in this space before, I would have made "sealed" the default.Anonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
Re: C/C++ pointers: Actually "int *x;" is in my opinion the more correct idiom in C++. Read the grammar carefully and you will see that in a local variable declaration, the "int" is the "decl specifier" and the "*x" is the "declarator". I also find C/C++ declarations confusing for the same reason. The way I have learned to think about them is to temporarily suspend the natural tendancy to look at it as a variable's type followed by its name. Rather, the way to conceptualize it is to simly say to yourself "x is some entity which has the property that *x is an integer variable". Similarly, "int x[]" means "x is some entity such that x[y] is an int variable", and so on. C# does not suffer from this problem -- in C#, the type always goes on the left and the variable's name goes on the right, end of story. And because all "type modifiers" (?, [], *) in C# go to the right end of the type declaration, there is never a need to parenthesize a type. (In C, is int *x[] a pointer to an array of ints or an array of int pointers? You might have to parenthesize to get the one you want.)Anonymous
January 21, 2009
Of course it's a C&C reference. :-)Anonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
I remember when I needed to create my first additional Thread in C#. I had seen it in Java (defining some inline classes, implementing Thread classes or Runnable interfaces) and was quite surprised, that I could just pass the name of the thread's function as a parameter - without any quotationmarks or other symbols. Better than ugly inline classes and better than some kind of string. In contrast we have throw new ArgumentNullException("paramName"); Refactoring will not detect this if I rename the parameter. If something is wrong with the type of a class i throw an WrongSuperclassException(obj.getType(), typeof(MyClass)), but I cannot do ArgumentNullException(var(paramName)). This felt wrong. Well, but as long as C# won't get any AOP or compile-time attributes this will never be a big issue. Until then we can still use "Strings" for things that are not strings but code.Anonymous
January 21, 2009
Two things come to mind. First off in LINQ you can use the equals operator (==) in the 'where' bit, but not in the 'join' bit. In the 'join' bit you need to use the keyword 'equals'. That just makes LINQ look really inconsistent to me. Secondly on my first experience with WCF, I found out the data object serializer makes the XML order dependent. Using ASMX the following XML fragments are treated equal when deserializing: <root> <firstChild/> <secondChild/> </root> and: <root> <secondChild/> <firstChild/> </root> In WCF, with the default serializer, they are not. I was always taught that XML should be order independent and that order dependency in an XML document is a bad design.Anonymous
January 21, 2009
@ Andrew Jenner: "In C, pointers are usually declared something like "int x;" suggesting that you're creating a variable whose type is "int" and whose name is "x". That's sort of what you're doing except not really at all - at this point you haven't created any variable of type "int". The C++ way of declaring pointers, "int x;" (suggesting you're creating a variable of type "int" whose name is "x") makes much more sense." There is one very good reason to stick with "int x;". Namely, if you write: int x, y; this suggests that both x and y are pointers, but in fact only x is a pointer!Anonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
If you're really motivated, you could do worse than take a little trip over to the StackOverflow site. I imagine that there are enough questions there to stop you getting bored.Anonymous
January 21, 2009
public enum someEnum
{
first = 1,
last = 2
} Enum.IsDefined(defaul(someEnum)) == false That's not intuitive to me that a default value is undefined [[ ERIC : The default value of an enum is always zero. The design guidelines call out that you should always support a zero value in your enum. ]]Anonymous
January 21, 2009
another one would be that the below code can't compile: int i=5, j; long h=10; public void Do(){ j = i/h; } the type of i/h is resolved to long but a division with integers can never result in a numerical larger result than the first argument. with that in mind int/long will always fit with into an int(?).Anonymous
January 21, 2009
The comment has been removedAnonymous
January 21, 2009
Not about C#, but it happened to me yesterday so it's fresh in my mind. In SQL Server, insert triggers are fired even if no data is actually being inserted into a table. When using a statement of the form "INSERT INTO a SELECT * FROM b", if the SELECT operation returns no rows, the insert trigger still fires but with no rows in the "inserted" table. Took me a while to figure that one out.Anonymous
January 21, 2009
Mine comes from Delphi (which is a bit of a mess syntactically all round). I would expect equality operators to have higher precedence than logical operators but they don't! So the following conditional: if a = b and isValid(c) then ... is interpreted by delphi as if (a) = (b and isValid(c)) then ... rather than the more natural, and more usual: if (a = b) and (isValid(c)) then ... Always throws me. And why would you ever want a logical operator in the lvalue or rvalue of an equality? Bizare!Anonymous
January 21, 2009
I've got a really simple one, which I've seen bite several projects: In .NET it's almost always a mistake not to Dispose an IDisposable - however, the language doesn't warn you if you do things like fileInfo.OpenText().ReadToEnd() - but doing so will cause non-deterministic "file-locked" failures when done repeatedly. In extreme, I've seen it cause deeply entreched architectural failures, where new db connections and associated readers are made and passed around without thought of disposal. Since the use of these connections is not local to the method in this example, refactoring to fix this issue is complicated. A compiler warning would be most appreciated!Anonymous
January 21, 2009
My first "Hmmm" moment in C# was when I read on Shawn Hargreave's blog that using enums as Dictionary keys will cause boxing. I didn't rest until I solved that problem and I found it a rather entertaining challenge, as it allowed me to learn fun stuff about C#. My latest "Hmmm" moment in C# was when I tried the following code: [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class UniqueIDAttribute : Attribute { private static int nextID = 1; public int ID { get; private set; } public UniqueIDAttribute() { ID = nextID++; } } public class Base { [UniqueID] public virtual void MyMethod() { } } public class Derived : Base { public override void MyMethod() { base.MyMethod(); } } class Program { static void Main(string[] args) { UniqueIDAttribute attr; attr = (UniqueIDAttribute)Attribute.GetCustomAttribute(typeof(Derived).GetMethod("MyMethod"), typeof(UniqueIDAttribute)); Console.Write(attr.ID); Console.Write(" "); attr = (UniqueIDAttribute)Attribute.GetCustomAttribute(typeof(Base).GetMethod("MyMethod"), typeof(UniqueIDAttribute)); Console.WriteLine(attr.ID); } } Imagine my surprise when the output was "1 2", instead of "1 1" I expected. Of course, once I sat down and thought about it, the reasons behind this became obvious. I still think it would be helpful to have it mentioned somewhere in the documentation ;)Anonymous
January 21, 2009
There was some nuance to foreach statements that left me perplexed for a little while. Specifically, the fact that the compiler can't really do type checks if either the type of the IEnumerable or the type in the foreach is an interface. For example: interface IFoo { ... } interface IBar { ... } public static void Main(string[] args) { IList<IFoo> foos = ...; foreach (IBar bar in foos) { ... } } This does not throw a compile error, and I expected it would. I eventually looked at the IL and figured out that the foreach gets mapped to an enumerator call and it casts the objects returned from the enumerator to the type specified in the foreach.Anonymous
January 21, 2009
The comment has been removedAnonymous
January 22, 2009
I rather find C# intuitive the one thing I have always had problem with though is a part of the framework. System.DirectoryServices That has to be one of the most confusing things out there. Not only in how it was written but how it works. For Example when ever you create a new directory entry you have to put it in a singleton pattern this is the only way I have figured out to keep it from leaking. A directory entry once create never goes away until the application closes but you put that in something like a windows service and over a week you will have hundreds of thousands of Directory Entries, even if you specifically set them to null and garbage collect them. Then just how you have to work with it in general and how you get things from it. Some active directory object you just can not get there is no definition for them. For the most part I find C# and the framework though a really enjoyable experience because it makes sense. Another thing though that is just really wacky is VISTO the tools for office. That has no logic to it.Anonymous
January 22, 2009
The comment has been removedAnonymous
January 22, 2009
OK, here is something that I learned about Objective-C the other day. I'm not sure that I like this feature, but it is interesting. Objective-C has both garbage collected and non-garbage collected options. When you use the non-garbage collected option, a peculiar type of duck typing kicks in. any methods that do not have a specific naming pattern (start with "alloc", "new" or "copy") and return objects will auto-release those objects when the current auto release pools goes out of scope.Anonymous
January 22, 2009
The comment has been removedAnonymous
January 22, 2009
This one's from VB, including recent VB.Net versions. Quick: pop question. How many elements does will this array have: Dim MyArray(4) As String Correct Answer: 5.Anonymous
January 22, 2009
The comment has been removedAnonymous
January 22, 2009
I've never found the ordering of generic contstraints intuitive: class SomeClass<T> where T: class, SomeBaseClass, IDisposable, new() { } Why does "class" have to come first, but "new()" have to come last? Why does a concrete class have to come before any interfaces? Why can't I just put them in any order? It's probably just me, but I can't come up with a paradigm so that the ordering makes sense.Anonymous
January 22, 2009
feature request: compile time reflection and code templates! i'm always annoyed when i have to implement INotifyPropertyChanged. It is always the same: add a protected virtual method OnPropChanged(string) and make alle property setters call this method with their name as parameter and only if value != old(value). i'd like to do this: <CodeGen_NotifyPropChanged> class DataObject { } and CodeGen_NotifyPropChanged is defined as: transform CodeGen_NotifyPropChanged for class { body(class c) { foreach(StaticProperty p in c.Properties) { StaticStatement s = <% OnPropertyChanged(%> StaticString(p.Name) <% ) %> p.Setter.AppendBody(s); } } } this would append OnPropChanged("Name") to each property setter. how to make this work is, again, your job ;-) but think about the general applicability! for example: Exception processing, logging, transaction management, security, and of course other laborous but uninteresting work. i imagine workig with a set of static types that only exist in the compiler, like StaticString, StaticProperty... i think as long as you are constrained to static types in your code template it would be pretty safe.Anonymous
January 22, 2009
That events aren't instantiated, so that you have to do a null check before you call one. It means you have to use template code every time, and tripped me up when I first used them. That generics can't have an "Enumeration" constraint. That generics can't have a "ThisType" type, so that MyGenericClass.FactoryMethod<ThisType>() could be defined in the baseclass to return whatever the class is. That the switch statement in C# isn't as flexible as the equivalent in VB.NETAnonymous
January 22, 2009
When I first came to C# (from C++) I was totally surprised to find that 'struct' and 'class' were very different in C#, since they are almost the same in C++. More recently I was surprised to find that even apparently immutable structs can change: http://tinyurl.com/dbeosu. I get it now, but I was very surprised. [[ ERIC: In your example, "bar" is a variable, so it should not be surprising that its contents change. That's what "variable" means. You can put an immutable struct into a mutable variable; if you change what is in the variable, a method called on the variable will of course change its behaviour. All an immutable struct guarantees is that its internals cannot be edited piecemeal in any unit of storage; they have to be edited all together by changing the whole storage, as you've done. The oddity here is not that immutable structs can change, because they cannot. The oddity is that closures capture variables, not values, which many people find unexpected. ]]Anonymous
January 22, 2009
Equality There is something deeply confusing about equality in C# and the .Net framework: x == y is not always the same as x.Equals(y) and x != y is not always the same as !(x == y) In one class you can override==
,!=
, andEquals(object)
, implement IEquatable and IEquatable(T), and then compare them using an IEqualityComparer(T) or IEqualityComparer. The situation is totally screwy.Anonymous
January 22, 2009
Not exactly a "Hmmm" moment, but something interesting and it might surprise somebody, but it actually compiles and runs: char str[6] = "Hello"; for (int i = 0; i < 5; i++) printf("%c == %cn", str[i], i[str]);Anonymous
January 22, 2009
It seems that the compiler sums the variables's address and the offset and it doesn't matter if their positions are swapped.Anonymous
January 23, 2009
"internal protected" semantics got me twice. I wish it had the other meaning: protected AND internal. If I remember correctly IL supports both, why not expose both in C#?Anonymous
January 23, 2009
Ivan: IIRC, a[b] is defined as *(a + b), and since + is commutative, it follows that a[b] == *(a + b) == *(b + a) == b[a]. So yes, it's confusing but legal.Anonymous
January 23, 2009
A big revelation for me was when learning Haskell at university, and suddenly understanding why there was such strange syntax for declaring a function type that took multiple arguments: multThree :: (Num a) => a -> a -> a -> a multThree x y z = x * y * z At first I thought "Why all the "->" arrows? Why aren't the inputs cleanly separated from the outputs in the type declaration?" I was uncomfortable with this syntax for several lectures until I realised that it's equivalent to this: multThree :: (Num a) => a -> (a -> (a -> a)) ...and you're defining one-argument functions that return functions! Of course, they got around to explaining this eventually, but they had to cover other stuff first, and all the time I knew there was something that wasn't quite right about my mental model.Anonymous
January 23, 2009
PL-SQL’s Lazy Evaluation can be non-intuitive. SELECT r.value random_value c.Name FROM (SELECT random(1000) value FROM DUAL) r, Customers c ; I would imagine that the subquery would result in a table with only one value, but it turns out every row in the result is has a different random_value!Anonymous
January 23, 2009
In C#, scoping for object initializers can look weird: public class SomeClass{
public string SomeProperty { get; set; }
public object Clone(
{
return new SomeClass { SomeProperty = SomeProperty };
}
} From an earlier comment by Eric: "The relevant part [of the spec] that explains this behaviour is the part that says that everywhere within the nearest enclosing block that a simple name is used, that name must be using unambiguously." The example above seems to violate this condition. SomeProperty on the LHS refers to the property of the new object. SomeProperty on the RHS refers to what's scoped in the current method. Wrote a blog article about it here: http://tinyurl.com/b47rta [[ ERIC: That does not violate the condition. As I carefully noted, the restriction is on simple names being used inconsistently. The identifier on the left hand side of an object initializer fragment is not a simple name, it is an identifier. There are a few ways you can use things that are not classified as simple names inconsistently; this is one. That said, I agree that this looks weird and I would avoid it. ]]Anonymous
January 23, 2009
Evil type coercion in VB is a good one: http://vb.mvps.org/articles/pt199511.pdfAnonymous
January 26, 2009
abstract class AbstractFoo { ... } class ConcreteFoo : AbstractFoo { ... } void DoFoo(IList<AbstractFoo> fooList) { ... } var fooList = new List<ConcreteFoo>(); DoFoo(fooList ); // COMPILE ERROR Then, after 1 minute, it occurs to me that List<AbstractFoo> and List<ConcreteFoo> are two very different objects, and chenged DoFoo to: void DoFoo<T>(IList<T> fooList) where T is AbstractFoo But at first, in my mind, my implementation looked very intuitive.Anonymous
January 29, 2009
The comment has been removedAnonymous
January 30, 2009
Mines another from the way back, and too far back into the late 80's for me to recall any exact details. It relates to an item of course work I had to do for my degree in Prolog. It wasn't the classic AI "Think of an animal" programme, but it wasn't that complex either. The revelation I had was about half way through coding the project, I suddenly realised I'd actually finished - because all of the statements/rules can be read as true in reverse too. Ended up being one of the few projects I could hand in early as a result as I'd drastically over estimated how much work it needed. I also remember first learning of object oriented programming reading a Turbo Pascal manual, and was amused when I'd finally had the penny drop and said "Ah ha!" out loud to myself, to turn the page (metaphorically - it may have just been the next paragraph) and find it said "When you've had your ah-ha moment..." - whoever wrote that knew exactly the right point when they'd explained enough for me to get it.Anonymous
February 03, 2009
I have recently read some article about C# 4.0 and its optional parameters (original article was not written in English so I don’t have link to it here). One thing from article I find very odd: If you have something like: public class SomeClass { public static string SaySomething(string s = "rrr") { return s; } } in one assembly (SomeLib.dll) and something like: class Program { static void Main(string[] args) { Console.WriteLine(SomeLib.SomeClass.SaySomething()); Console.WriteLine(SomeLib.SomeClass.SaySomething("abcd")); } } in the second (test.exe). A result of running test.exe is of course "rrr" and "abcd". But if you now change SaySomething()'s default parameter value e.g.: public static string SaySomething(string s = "rrr changed!!!") and then recompile SomeLib.dll (without recompiling test.exe) the result of running test.exe again (with new SomeLib.dll) would be same as in the first case (but not "rrr changed!!!", "abcd" as expected). This "feature" of optional parameters implementation is in my opinion undesirable.Anonymous
February 11, 2009
Why you can use property as an out param in a functionAnonymous
March 23, 2009
@holatom: options being bound at static link can be very useful: public class MyLib { public const string Version1 = "1.0"; public const string Version1_1 = "1.1"; public const string Version1_1_NoMagic = "1.1 (-magic)"; public static string Latest() { return Version1_1; } public MyLib(string version = Version1_1) {...} } If you want to have optional parameters that can have the default changed later, you have overloading: public class Greeter { public void SayHello() { SayHello("World"); } public void SayHello(string name) { ... } }Anonymous
April 29, 2009
Just revisited my "hmm" int/long is a long. There was a comment that the result actually might need a long int.MaxValue/(long)-1 will not fit into an int. however the compiler accepts int x = int.MaxValue/(int)-1 but the result can not be stored in an int. That makes me go hmmmAnonymous
May 25, 2009
That Typebuilder,Methodbuilder and all the other builders violate Liskov Substitution principle It seems rather trivial to implement the methods that in the builder version throws an exception like GetConstructors() on Typebuilder.