C# Corner
What VB Devs Should Know About C# Programming
Chances are C# is not your first programming language. Here are several tips that can help you leverage C# better if you already know VB.NET.
TECHNOLOGY TOOLBOX: VB.NET, C#
The VB and C# languages have a lot in common, and developers who specialize in one often find themselves having to write or manipulate code written in the other.
Kathleen Dollard -- author of VSM's Ask Kathleen Q&A-based column -- wrote a blog entry recently that laid out many of the challenges that arise when working with both C# and VB.NET, which led to an interesting discussion between us about developers who find themselves switching between C# and VB.NET. (You can read our discussion here).
Our discussion also covered the need for developers to get out of their comfort zones and learn something new. Yes, the two languages are similar in features, but there are idiomatic differences between the languages, and some tasks are significantly easier in one language than the other. In fact, you'll find yourself much more skilled and much more in demand if you know the best features in both languages, as well as how to build applications that rely on both languages.
In this column, I'll walk you through the differences you should be aware of as a VB developer using C#; be sure to read Dollard's column in this issue ("What C# Developers Should Know About VB") for a list of similar concerns when using VB as a C# developer.
One of the more important points for a VB developer using C# to keep in mind is that iterator methods using yield return and yield break make it easy to create methods that generate, examine, or modify sequences. For example, here's a method that generates all the letters of the alphabet:
public static IEnumerable<char> Letters()
{
char currentCharacter = 'a';
do
{
yield return currentCharacter;
} while (currentCharacter++ < 'z');
}
Iterator methods provide an excellent shorthand syntax for common patterns used with sequences. Rather than hand-coding a class that implements IEnumerator<T>, the compiler generates all that code for you because of the yield return statement.
Yield return provides the next value to the compiler-generated enumerator object. You use the yield break statement to stop an enumeration. For example, this method returns characters until a ‘ . ' is found:
public static IEnumerable<char>
EchoUntil(IEnumerable<char> inputSequence)
{
foreach (char value in inputSequence)
{
if (value != '.')
yield return value;
else
yield break;
}
}
As in this example, you typically use yield break along with yield return in an iterator method. This example illustrates one of the more powerful idioms available using iterator methods: sequence methods invoking lazy evaluation. When you combine iterator methods, you build a pipeline of transformations that let youperform multiple transformations on a sequence while only enumerating the source sequence once. You can see that at work in almost every LINQ query written (in either VB.NET or C#). Select, Skip, SkipWhile, Take, TakeWhile, and Where, among other methods defined in System.Linq.Enumerable, are implemented as iterator methods. They all accept an input sequence and return an output sequence. This means iterator methods are composable methods: You can put many methods together to work on a single sequence.
Iterator methods are one of the core building blocks used to create the LINQ functionality you might have already used in your favorite .NET language. From there, we'll move on to another LINQ enabling language feature.
Syntax for Lambdas and Closures Differs
Both C# and VB.NET support lambdas and closures. However, the syntax, and in some cases, the functionality, are different. In C#, the syntax for lambda expressions is more concise and flexible. Both compilers infer parameter types and return statements. For example, this statement uses a lambda expression to define the predicate for SkipWhile:
var largeNumbers = sequence.SkipWhile(
number => number < limit);
Let's put some stricter definitions on these terms. A predicate is a Boolean method that determines whether an element passes some condition. In this example, the predicate determines whether a number in the sequence is smaller than some limit. For more on this subject, see Dollard's column, "Understand Your Code Better" (October 2008).
The C# compiler examines the parameters and return types for SkipWhile, and then it creates parameter types that match the parameters list on your lambda expression. If "sequence" contains integers, "number" is an integer. If sequence contains doubles, number is a double.
Unlike VB.NET, C# allows multiple statement lambdas. For example, assume you want to create a sequence of numbers by adding five to the input sequence, and then squaring that number:
var manipulation = Enumerable.Range(0, 10).Select(n =>
{
int temp = n + 5;
temp = temp * temp;
return temp;
});
Of course, lambdas can get more complicated. The body of the lambda can access -- and even modify -- variables declared in the enclosing scope. You can modify the previous sample to return the next number added to the previous answer:
int previousAnswer = 0;
var manipulation = Enumerable.Range(0, 10).Select(n =>
{
int temp = n + previousAnswer;
previousAnswer = temp;
return temp;
});
Notice that the variable previousAnswer is declared in the outer scope, but it's modified in the inner scope of the lambda. The C# compiler does quite a bit of work to create a Closure that enables this functionality. One additional thing you probably want to keep in mind is that the C# compiler creates a nested class that enables the inner scope (the lambda) and the outer scope (the enclosing method) to share access to the same variables. You can find more information on closures in Dollard's October Ask Kathleen column.
Drill Down on Anonymous Types
Another important fact to keep in mind is that C# anonymous types are immutable. You cannot modify the values of any property on an instance of any anonymous type. This is similar to declaring every property as a key property in VB.NET anonymous types. All anonymous types in C# implement IEquatable<T>, and do so by comparing every property in that type. In VB.NET, only those properties you declare as key properties participate in the equality comparison. C# has its roots in C, where terseness was a virtue. This led to adding the conditional operator (?:) and the null coalescing operator (??). Both of these operators provide shorthand notation for relatively common operations. These operators can improve code readability when used reasonably, but have the exact opposite effect when overused.
The conditional operator is simple: It tests a condition and then evaluates one of two expressions based on the condition:
Activity stuffToDo = weekday ?
Activity.Work : Activity.Play;
The condition in this example is "weekday." If weekday is true, the expression Activity.Work is returned from the conditional operator. If weekday is false, the expression Activity.Play will be returned. It's equivalent to this if / else statement:
if (weekday)
stuffToDo = Activity.Work;
else
stuffToDo = Activity.Play;
The conditional operator is merely a simpler way to express this if / else logic when the intent is a simple assignment.
Microsoft introduced the same operator into VB.NET in VS 2008, so many VB.NET developers might not be familiar with it. The VB.NET syntax looks like this:
Dim stuffToDo = _
If(weekday, Activity.Work, Activity.Play)
The null coalescing operator works in the same manner when you want to examine a nullable type, and you want to assign a default value when the nullable object doesn't contain a value. This statement sets a magic number:
int MagicNumber = potentialMagicNumber ?? 5;
If potentialMagicNumber has a value, that becomes the magic number. If potentialMagicNumber doesn't have a value, magicNumber is set to five. You could write a more verbose version of that line like this:
MagicNumber = (
potentialMagicNumber.HasValue) ?
potentialMagicNumber.Value : 5;
Or this:
if (potentialMagicNumber.HasValue)
MagicNumber = potentialMagicNumber.Value;
else
MagicNumber = 5;
The terse VB.NET 2008 version uses the if operator, as well:
Dim magicNumber = If(potentialMagicNumber, 5)
I started this section by pointing out that there are anti-patterns for both of these constructs. The examples above are the simplest use of either operator. The anti-patterns come into play when developers decide to chain those operators. You can quickly develop unreadable code if you abuse these constructs:
int MagicNumber = potentialMagicNumber ??
potentialMagicNumber2 ??
potentialMagicNumber3 ??
potentialMagicNumber4 ??
potentialMagicNumber5 ??
potentialMagicNumber6 ??
potentialMagicNumber7 ??
potentialMagicNumber8 ?? 5;
This code is plain wrong. Using this kind of construct, where several different nullable objects are checked, is probably indicative of other problems; and it's never pretty.
Nullable Objects Compare Differently
Another point to bear in mind is that comparisons involving nullable objects work different in VB.NET and C#. In C#, the comparison semantics are consistent with NaN (not a number) comparisons. A nullable object that doesn't contain a value is neither greater than nor less than a nullable object that has a value. Of course, it's not equal either.
This code snippet prints False three times:
int? Something = 5;
int? nothing = default(int?);
Console.WriteLine(
nothing < Something);
Console.WriteLine(
Something < nothing);
Console.WriteLine(
Something == nothing);
In all three cases, the return value for those comparisons is a bool. Nullable objects that don't have a value don't have valid ordering comparisons. In VB.NET, all three comparisons would return a nullable Boolean (bool?) without a value. It's the same as the C# expression default(bool?). Dollard covers the VB.NET semantics in her column on p. 35.
C# uses a different toolset for conversions than VB.NET. C#'s toolset is smaller, but has similar functionality. C# allows using the keywords "is" and "as" -- as well as direct casts.
It's important that you be clear on your terms in this case, especially when distinguishing between "cast" and "conversion." A cast is when you look at the same object through a different reference, which enables you to look at an object differently. A conversion occurs when you create a new object of a different type.
C# uses the is and as operators to perform casts on reference types. The is operator tests an object (of a reference type) to see if a cast will succeed. It examines the runtime type of the object and tells you whether it can be interpreted as a particular type. The C# is operator is equivalent to the Typeof/Is operator in VB.NET. C#'s as operator examines the runtime type of an object (once again, only of a reference type) and attempts to view it as the specified type. The as operator succeeds only if the object being tested is the type being tested.
You can perform other casts and conversions using direct cast notation:
MyType thing = (MyType) someObject;
The direct cast succeeds if the object (someObject) is a MyType at runtime. It also succeeds if a conversion (either implicit or explicit) exists between the compile time type of someObject and MyType. A direct cast results in one of three outcomes. First, thing can refer to the same object as someObject, but now as a different type. Second, thing can refer to a new object that was created by converting someObject into a MyType object. For example, assume you type this line:
int val = (int) Math.Pi;
Math.Pi (3.14…) is converted to an integer (3) and that value is assigned to val. It's a new storage location, not a new reference to the same bits.
The third outcome of a direct cast is an InvalidCastException if the cast fails and no conversion is available.
There are a handful of additional differences worth keeping in mind. First, default() is closer to Nothing than null. C# has slightly different ways to express Nothing than VB.NET. In C#, Nothing is close to the default(Type) statement:
string nada = default(string); // nada is null.
int zip = default(int); // zip is 0
Null is a literal that specifies a reference that does not refer to any object. You can't use null with value types. In practice, default() is a better choice in most cases than null, especially in generic types. Null can force you to add the class constraint.
Second, there are differences in casing and auto-implemented properties. Traditionally, C# developers would create properties with backing fields that had the same name:
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
This approach saved C# developers from the need to come up with two different names for the same concept. Now, it's even easier with auto-implemented properties. The compiler creates the backing field for you:
public string Name
{
get;
set;
}
Note that C# lets you create properties that can be set only by the same class by adding an access modifier on the setter:
public string Name
{
get;
private set;
}
I've covered the most common areas where VB.NET developers get tripped up when they use C# for the first time. Yes, you can solve the same problems in both languages, but you'll often do so in distinctly differently ways. If you try to write VB.NET code in C#, it will not be efficient. The same is true for C# developers trying to write C# in VB.NET. If you're a regular C# developer, and you find that you might need to use VB.NET, be sure to read Dollard's column, which will help you immensely. In fact, even if you're not about to code in VB.NET, go read Dollard's column anyway. Knowing some of the features of VB.NET will make you a better developer, period.
About the Author
Bill Wagner, author of Effective C#, has been a commercial software developer for the past 20 years. He is a Microsoft Regional Director and a Visual C# MVP. His interests include the C# language, the .NET Framework and software design. Reach Bill at [email protected].