Home > Articles > Programming > Visual Studio

The .NET Languages

This chapter is from the book

Language Features

Thus far, you’ve looked at the basics of programming with the .NET languages, including building objects and solving common coding issues with respect to looping, handling logic, and creating and consuming events. This section points out some additional elements that make the .NET languages special. Many of these items are not necessarily things you might use every day; however, they can provide you with additional skills when writing code and better understanding when reading it. The .NET language features covered here include the following:

  • Local type inference (also called implicit typing)
  • Object initializers
  • Collection initializers
  • Extension methods
  • Anonymous types
  • Lambda expressions
  • Partial methods
  • Language Integrated Query (LINQ)
  • Friend assemblies
  • XML language support
  • Unused event arguments
  • Automatically implemented properties
  • Implicit line continuation in VB
  • Work with dynamic language/objects
  • Covariance and contravariance
  • Intrinsic support for async operations
  • Type equivalence support

Infer a Variable’s Data Type Based on Assignment

In the later versions of Visual Basic and C# (2008 and later), you can define variables without explicitly setting their data type. And, when doing so, you can still get the benefits of strongly typed variables (compiler checking, memory allocation, and more). The compilers actually infer the data type you intend to use based on your code. This process is called local type inference or implicit typing.

For example, consider the following lines of code. Here you create a variable of type String and assign a value.

C#

string companyName = "Contoso";

VB

Dim companyName As String = "Contoso"

Now, let’s look at the same line of code using type inference. You can see that you do not need the string portion of the declaration. Instead, the compiler is able to determine that you want a string and strongly types the variable for you. In C#, this is triggered by the keyword var. This should not be confused with the var statement in languages such as JavaScript. Variables defined as var are strongly typed. In Visual Basic, you still simply use the Dim statement but omit the data type.

C#

var companyName = "Contoso";

VB

Dim companyName = "Contoso"

These two lines of code are equivalent in all ways. Although in the second example no data type was declared, one is being declared by the compiler. This is not a return to a generalized data type such as Variant or Object. Nor does this represent late-binding of the variable. Rather, it is simply a smarter compiler that strongly types the variable by choosing a data type based on the code. You get all the benefits of early-bound variables while saving some keystrokes.

For example, take a look at Figure 3.3. This is the C# compiler in action. (The Visual Basic compiler does the same thing.) You can see that even at development time, the compiler has determined that this variable is of type System.String.

FIGURE 3.3

FIGURE 3.3 Type inference in action inside the IDE.

There are a few things for you to be aware of when using type inference. The first is that it requires your local variable to be assigned a value to do the compiler typing. This should not be a big deal because if your variable is not assigned, it is not used.

The second item you should consider is that type inference works only with local variables. It does not work with class-level variables (also called fields) or static variables. In these cases, using local type inference results in the compiler throwing an error in C#. In Visual Basic, you would get the same error provided that Option Strict is set to On. If you are not using Option Strict in your Visual Basic code, the variable is not strongly typed. Instead, the variable is assigned the generic Object data type.

Local type inference can be useful in other declaration scenarios as well. This includes defining arrays, creating variables during looping, defining a variable inside a Using statement, and defining a variable that contains the result of a function call. In each of these cases, the compiler can infer your data type based on the context of the code.

As another example, the following code creates a Using statement and infers the type of the variable cnn (as a SqlConnection object). Note that a Using block defines a block of code for which a given resource is being used. The use of a Using block guarantees that the runtime disposes of the used object (in this case, the database connection) when done.

C#

using (var cnn = new System.Data.SqlClient.SqlConnection()) {
  //code to work with the connection
}

VB

Using cnn = New System.Data.SqlClient.SqlConnection
  'code to work with the connection
End Using

In Visual Basic, you can turn local type inference off and on for a given file. By default, a new Visual Basic code file is set to allow type inference. However, if you want to turn it off at the file level, you can do so by setting Option Infer Off at the top of the code file.

Create an Object and Initialize Its Values (Object Initializers)

There is a shortcut for both declaring an instance of a class and setting the initial value of all or some of its members. With a single line of code, you can instantiate an object and set a number of properties on that object. During runtime, the object is created, and then the properties are set in the order in which they appear in the initialization list. This feature is called object initializers.

Let’s look at an example. Suppose you have a class called Employee that has a number of properties such as FirstName, LastName, FullName, Title, and the like. Using object initialization, you can both create an instance of this class and set the initial values of some (or all) of the Employee instance’s properties. To do so, you first construct the object. In Visual Basic, you follow this construction with the With keyword. (C# does not require an equivalent indicator.) You then place each property initialization inside a set of curly braces. Examples are as shown here.

C#

Employee emp = new Employee { FirstName = "Joe",
  LastName = "Smith", Title = "Sr. Developer" };

VB

Dim emp As New Employee With {.FirstName = "Joe", _
  .LastName = "Smith", .Title = "Sr. Developer"}

This single line of code is the equivalent of first creating an Employee class and then writing a line of code for each of the listed properties. Notice that in Visual Basic, you access each property using a dot. In C#, you do not need the dot.

Of course, you can also use object initialization with parameterized constructors. You simply pass the parameters into the constructor as you normally would. You then follow the constructor with the initialization. For example, suppose that the Employee class had a constructor that took the first and last name, respectively. You could then create the object with the parameters and use object initialization for the Title, as shown here.

C#

Employee emp = new Employee("Joe", "Smith")
  { Title = "Sr. Developer" };

VB

Dim emp As New Employee("Joe", "Smith") With _
  {.Title = "Sr. Developer"}

Object initialization also enables you to write some code in the initialization. In addition, with Visual Basic you can use properties of the object you are initializing to help initialize other properties. This is not valid in C#. The C# compiler does not allow you to access the variable until the assignment is complete. To see an example of this, the following code initializes an Employee object and sets the Employee.FullName property by concatenating the first and last names. Notice that the Visual Basic code uses the object itself.

C#

Employee emp = new Employee { FirstName = "Joe",
  LastName = "Smith", FullName = "Joe" + " Smith"};

VB

Dim emp As New Employee() With {.FirstName = "Joe", _
  .LastName = "Smith", _
  .FullName = .FirstName & " "" & .LastName}

You can also nest object initialization. That is, if a given property represents another object, you can create the other object as part of the initialization. You can also nest an initialization of the other object within the initialization of the first object. A simple example makes this clear. Suppose that the Employee class has a property called Location. The Location property might point to a Location object that includes the properties for City and State. You could then create the Employee object (along with the nested Location object), as shown here.

C#

Employee emp = new Employee { FirstName = "Joe",
  LastName = "Smith", Location = new Location
  { City = "Redmond", State = "WA" } };

VB

Dim emp As New Employee() With {.FirstName = "Joe", _
  .LastName = "Smith", _
  .Location = New Location With _
  {.City = "Redmond", .State = "Washington"}}

Define a Collection and Initialize Its Values

You can now define a collection class or an array and, at the same time, set the initial values in your object. This turns multiple lines of code calling simple add methods into a single line. This is especially useful if you have a list of items that your application works with and you need to both declare the list and initialize these values.

For example, you might need to define an array to contain the geographic locations for your sales office. You could define this array and initialize it as follows.

C#

string[] salesGeos = {"South", "Mid Atlantic", "Mid West"};

VB

Dim salesGeos() As String = {"South", "Mid Atlantic", "Mid West"}

You can use similar syntax to define and initialize a collection class, including those based on a generic. For example, the following defines a list of Employee objects and adds two new Employee classes to that list. Note that the Visual Basic code requires the From keyword.

C#

List<Employee> empList = new List<Employee>
  {new Employee("1234"), new Employee("3456")};

VB

Dim empList As New List(Of Employee) From _
  {New Employee("1234"), New Employee("3456")}

Creating an Instance of a Nonexistent Class

The .NET languages enable you to create an object that does not have a class representation at design time. Instead, an unnamed (anonymous) class is created for you by the compiler. This feature is called anonymous types. Anonymous types provide crucial support for LINQ queries. With them, columns of data returned from a query can be represented as objects (more on this later). Anonymous types are compiled into class objects with read-only properties.

Let’s look at an example of how you would create an anonymous type. Suppose that you want to create an object that has both a Name and a PhoneNumber property. However, you do not have such a class definition in your code. You could create an anonymous type declaration to do so, as shown here.

C#

var emp = new { Name = "Joe Smith",
  PhoneNumber = "123-123-1234"};

VB

Dim emp = New With {.Name = "Joe Smith", _
  .PhoneNumber = "123-123-1234"}

Notice that the anonymous type declaration uses object initializers (see the previous discussion) to define the object. The big difference is that there is no strong typing after the variable declaration or after the New keyword. Instead, the compiler creates an anonymous type for you with the properties Name and PhoneNumber.

There is also the Key keyword in Visual Basic. It is used to signal that a given property of an anonymous type should be used by the compiler to further define how the object is treated. Properties defined as Key are used to determine whether two instances of an anonymous type are equal to one another. C# does not have this concept. Instead, in C# all properties are treated like a Visual Basic Key property. In Visual Basic, you indicate a Key property in this way.

Dim emp = New With {Key .Name = "Joe Smith", _
  .PhoneNumber = "123-123-1234"}

You can also create anonymous types using variables (instead of the property name equals syntax). In these cases, the compiler uses the name of the variable as the property name and its value as the value for the anonymous type’s property. For example, in the following code, the name variable is used as a property for the anonymous type.

C#

string name = "Joe Smith";
var emp = new {name, PhoneNumber = "123-123-1234" };

VB

Dim name As String = "Joe Smith"
Dim emp = New With {name, .PhoneNumber = "123-123-1234"}

Add Methods to Existing Classes (Extension Methods)

You can add custom features to an existing type as if the type always had the custom features. In this way, you do not have to recompile a given class, nor do you have to create a second derived class to add these features. Rather, you can add a method to an existing class by using a compiler feature called extension methods.

Adding methods varies between Visual Basic and C#. In Visual Basic, you first import the System.Runtime.CompilerServices namespace into your code file. Next, you mark a given Sub or Function with the ExtensionAttribute directive. Lastly, you write a new Sub or Function with the first parameter of the new method being the type you want to extend. The following shows an example. In this example, we extend the Integer type with a new method called DoubleInSize. The compiler knows we are extending the Integer class because this method is marked as Extension, and the first parameter in the method takes an Integer value.

VB

Imports System.Runtime.CompilerServices

Public Module IntegerExtensions
  <Extension()>
  Public Function DoubleInSize(ByVal i As Integer) As Integer
    Return i + i
  End Function
End Module

The C# compiler does not require the same import or method attribute. Instead, you first create a static class. Next, you create a static method that you intend to use as your extension. The first parameter of your extension method should be the type you want to extend. In addition, you apply the this modifier to the type. Notice the following example. In it, we extend the int data type with a new method called DoubleInSize:

C#

namespace IntegerExtensions
{
  public static class IntegerExtensions
  {
    public static int DoubleInSize(this int i)
    {
      return i+i;
    }
  }
}

To use an extension method, you must first import (using in C#) the new extension class into a project. You can then call any new method as if it had always existed on the type. The following is an example in both Visual Basic and C#. In this case, a function called DoubleInSize that was defined in the preceding example is being called from the Integer (int) class.

VB

Imports IntegerExtensions

Module Module1
  Sub Main()
    Dim i As Integer = 10
    Console.WriteLine(i.DoubleInSize.ToString())
  End Sub
End Module

C#

using IntegerExtensions;

namespace CsEnhancements
{
  class Program
  {
    static void Main(string[] args)
    {
      int i = 10;
      Console.WriteLine(i.DoubleInSize().ToString());
    }
  }
}

Add Business Logic to Generated Code (Partial Methods)

A partial method (like a partial class) represents code you write to be added as a specific method to a given class upon compilation. This enables the author of a partial class to define an empty method stub and then call that method from other places within the class. If you provide implementation code for the partial method stub, your code is called when the stub would be called (actually the compiler merges your code with the partial class into a single class). If you do not provide a partial method implementation, the compiler goes a step further and removes the method from the class along with all calls to it. This is why such as partial method returns void and cannot take out parameters.

The partial method (and partial class) was created to aid in code generation and should generally be avoided unless you are writing code generators or working with them because they can cause confusion in your code.

Of course, Visual Studio has more and more code generation built in. Therefore, it is likely you will run into partial methods sooner or later. In most cases, a code generator or designer (such as LINQ to SQL) generates a partial class and perhaps one or more partial methods. The Partial keyword modifier defines both partial classes and partial methods. If you are working with generated code, you are often given a partial class that allows you to create your own portion of the class (to be merged with the code-generated version at compile time). In this way, you can add your own custom business logic to any partial method defined and called by generated code.

Let’s look at an example. The following represents an instance of a partial class Employee. Here there is a single property called Salary. In addition, there is a method marked Partial called SalaryChanged. This method is called when the value of the Salary property is modified.

C#

partial class Employee {

  double _salary;

  public double Salary {
    get {
      return _salary;
    }
    set {
      _salary = value;
      SalaryChanged();
    }
  }

  partial void SalaryChanged();
}

VB

Partial Class Employee

  Private _salary As Double

  Property Salary() As Double
    Get
      Return _salary
    End Get
    Set(ByVal value As Double)
      _salary = value
      SalaryChanged()
    End Set
  End Property

  Partial Private Sub SalaryChanged()
  End Sub

End Class

The preceding code might represent code that was created by a code generator. The next task in implementing a partial method then is to define another partial Employee class and provide behavior for the SalaryChanged method. The following code does just that.

C#

partial class Employee
{
  partial void SalaryChanged()
  {
    double newSalary = this.Salary;
    //do something with the salary information ...
  }
}

VB

Partial Class Employee
  Private Sub SalaryChanged()
    Dim newSalary As Double = Me.Salary
    'do something with the salary information ...
  End Sub
End Class

When the compiler executes, it replaces the SalaryChanged method with the new partial method. In this way, the initial partial class (potentially code generated) made plans for a method that might be written without knowing anything about that method implementation. If you decide to write it, it is called at the appropriate time. However, it is optional. If you do not provide an implementation of the partial method SalaryChanged, the compiler strips out the method and the calls to the method (as if they had never existed). This provides similar services to the virtual/override mechanisms presented earlier in this chapter.

Access and Query Data Using the .NET Languages

Visual Studio 2008 introduced the language feature set called LINQ. LINQ is a programming model that takes advantage of many of the features discussed in this section. It provides language extensions that change the way you access and work with data. With it, you can work with your data using object syntax and query collections of objects using Visual Basic and C#.

You can use LINQ to map between data tables and objects. (See Chapter 13, “Working with Databases.”) In this way, you get an easier, more productive way to work with your data. This includes full IntelliSense support based on table and column names. It also includes support for managing inserts, updates, deletes, and reads.

The last of these, reading data, is a big part of LINQ in that it has built-in support for easily querying collections of data. Using LINQ features, you can query not only your data but also any collection in .NET. There are, of course, new keywords and syntax for doing so. Query operators that ship with Visual Basic, for example, include Select, From, Where, Join, Order By, Group By, Skip, Take, Aggregate, Let, and Distinct. The C# language has a similar set of keywords. And, if these are not enough, you can extend the built-in query operators, replace them, or write your own.

You use these query operators to query against any .NET data that implements the IEnumerable or IQueryable interface. This may include a DataTable, mapped SQL Server objects, .NET collections (including Generics), DataSets, and XML data.

Let’s look at an example. Suppose you had a collection of employee objects called employees and you wanted to access all the employees at a specific location. To do so, you might write the following function.

C#

public static IEnumerable<Employee> FilterEmployeesByLocation
  (IEnumerable<Employee> employees, string location)
{
  //LINQ query to return collection of employees filtered by location
  var emps = from Employee emp in employees
              where emp.Location.City == location
              select emp;

  return emps;
}

VB

Public Shared Function FilterEmployeesByLocation(
  ByVal employees As IEnumerable(Of Employee),
  ByVal location As String) As IEnumerable(Of Employee)

  'LINQ query to return collection of employees filtered by location
  Dim emps = From Employee In employees
             Where Employee.Location.City = location

  Return emps

End Function

Take a look at what is going on in the previous listing. The function takes a list of employee objects, filters it by a region passed to it, and then returns the results. Notice that to filter the list we create a LINQ in-memory query called emps. This query can be read like this: Looking at all the employee objects inside the employees collection, find those whose city matches the city passed into the function. Finally, we return emps as an IEnumerable<T> to allow the calling client to cycle through the results.

This is just a brief overview of LINQ. There are many things going on here, such as compile-time checking and schema validation (not to mention the LINQ language syntax). You will undoubtedly want to spend more time with LINQ.

Write Simple Unnamed Functions Within Your Code (Lambda Expressions)

The latest versions of the .NET languages (2008 and later) enable you to write simple functions that might or might not be named, execute inline, and return a single value. These functions exist inside your methods and not as separate, standalone functions. These functions are called lambda expressions. It’s useful to understand lambda expressions because they are used behind the scenes in LINQ queries. However, they are also valid outside of LINQ.

Let’s take a look at an example. Suppose that you want to create a simple function that converts a temperature from Fahrenheit to Celsius. You could do so within your Visual Basic code by first using the keyword Function. Next, you could indicate parameters to that function (in this case, the Fahrenheit value). Lastly, you could write an expression that evaluates to a value that can be returned from the lambda expression. The syntax is as follows.

VB

Dim fahToCel = Function(fahValue As Integer) ((fahValue - 32) / 1.8)

The C# syntax is a bit different. In C#, you must explicitly declare a delegate for use by the compiler when converting your lambda expression. Of course, you declare the delegate at the class-level scope. After you have the delegate, you can write the expression inside your code. To do so, you use the => operator. This operator is read as “goes to.” To the left side of the operator, you indicate the delegate type, a name for the expression, and then an = sign followed by any parameters the expression might take. To the right of the => operator, you put the actual expression. The following shows an example of both the delegate and the expression.

C#

//class-level delegate declaration
delegate float del(float f);

//lambda expression inside a method body
del fahToCel = (float fahValue) => (float)((fahValue - 32) / 1.8);

Notice that in both examples, we assigned the expression to a variable fahToCel. By doing so, we have created a delegate (explicitly converting to one in C#). We can then call the variable as a delegate and get the results, as shown here.

C#

float celcius = fahToCel(-10);

VB

Dim celcius As Single = fahToCel(70)

Alternatively, in Visual Basic, we could have written the function inline (without assigning it to a variable). For example, we could have written this.

VB

Console.WriteLine((Function(fahValue As Integer) ((fahValue - 32) / 1.8))(70))

Notice in this last example that the function is declared and then immediately called by passing in the value of 70 at the end of the function.

The C# language has its own quirk, too. Here you can write multiple statements inside your lambda expression by putting the statements inside curly braces and setting off each statement with a semicolon. The following example has two statements inside the lambda expression. The first creates the new value; the second writes it to a console window. Notice, too, that the delegate must be of type void in this instance and that you still must call the lambda expression for it to execute.

C#

//class level delegate declaration
delegate void del(float f);

del fahToCel = (float fahValue) => { float f =
  (float)((fahValue - 32) / 1.8); Console.WriteLine(f.ToString()); };
fahToCel(70);

Lambda expressions are used in LINQ queries for things such as the Where, Select, and Order by clauses. For example, using LINQ, you can write the following statement.

C#

var emps = from emp in db.employees
  where emp.Location == "Redmond"
  select emp;

VB

Dim emps = From emp In db.employees
  Where(emp.Location = "Redmond")
  Select emp

This LINQ code gets converted to lambda expressions similar to this.

C#

var emps = from emp in db.employees
  where (emp => emp.Location == "Redmond")
  select (emp => emp);

VB

Dim emps = From emp In db.employees.Where(Function(emp) emp.Location = _
  "Redmond").Select(Function(emp) emp)

Splitting an Assembly Across Multiple Files

The 2005 version of C# introduced the concept of friend assemblies; the feature was added to Visual Basic in 2008. It enables you to combine assemblies in terms of what constitutes internal access. That is, you can define internal members but have them be accessible by external assemblies. This capability is useful if you intend to split an assembly across physical files but still want those assemblies to be accessible to one another as if they were internal.

You use the attribute class InternalsVisibleToAttribute to mark an assembly as exposing its internal members as friends to another assembly. This attribute is applied at the assembly level. You pass the name and the public key token of the external assembly to the attribute. The compiler then links these two assemblies as friends. The assembly containing InternalsVisibleToAttribute exposes its internals to the other assembly (and not vice versa). You can accomplish the same thing by using the command-line compiler switches.

Friend assemblies, like most things, come at a cost. If you define an assembly as a friend of another assembly, the two assemblies become coupled and need to coexist to be useful. That is, they are no longer a single unit of functionality. This can cause confusion and increase management of your assemblies. It is often easier to stay away from this feature unless you have a specific need.

Working with XML Directly Within Your Code (VB Only)

You can embed XML directly within your Visual Basic code. This can make creating XML messages and executing queries against XML a simple task in Visual Basic. To support this feature, Visual Basic enables you to write straight XML when using the data types called System.Xml.Linq.XElement and System.Xml.Linq.XDocument. The former enables you to create a variable and assign it an XML element. The latter, XDocument, is used to assign a variable to a full XML document.

Writing XML within your Visual Basic code is a structured process and not just simple strings assigned to a parsing engine. In fact, the compiler uses LINQ to XML behind the scenes to make all this work. Let’s look at a simple example. The following code creates a variable emp of type XElement. It then assigns the XML fragment to this variable.

VB

Dim emp As XElement = <employee>
                          <firstName>Joe Smith</firstName>
                          <title>Sr. Developer</title>
                          <company>Contoso</company>
                          <location>Redmond, WA</location>
                      </employee>

You can create a similar fragment to an XDocument. You simply add the XML document definition (<?xml version="1.0"?>) to the header of the XML. In either scenario, you end up with XML that can be manipulated, passed as a message, queried, and more.

Visual Basic enables you to write XML inside your code. The two objects (XElement and XDocument) are still important to C# developers. However, C# developers work with the properties and methods of these objects directly and do not rely on the editor to parse XML directly within a code window. The following shows the same code sample written using C#. (You need the using statement using System.Xml.Linq;.)

C#

XElement xmlTree1 = new XElement("employee",
    new XElement("firstName", "Joe Smith"),
    new XElement("title", "Sr. Developer"),
    new XElement("company", "Contoso"),
    new XElement("location", "Redmond, WA")
);

In most scenarios, however, you do not want to hard-code your XML messages in your code. You might define the XML structure there, but the data comes from other sources (variables, databases, and so on). Thankfully, Visual Basic also supports building the XML using expressions. To do so, you use an ASP-style syntax, as in <%= expression %>. In this case, you indicate to the compiler that you want to evaluate an expression and assign it to the XML. For XML messages with repeating data, you can even define a loop in your expressions. For example, let’s look at building the previous XML using this syntax. Suppose that you have an object e that represents an employee. In this case, you might write your XElement assignment as shown here.

VB

Dim e As Employee = New Employee()
Dim emp As XElement = <employee>
                          <firstName><%= e.FirstName %></firstName>
                          <lastName><%= e.LastName %></lastName>
                          <title><%= e.Title %></title>
                          <company><%= e.Company %></company>
                          <location state=<%= e.Location.State %>>
                              <%= e.Location.City %>
                          </location>
                      </employee>

Removing Unused Arguments from Event Handlers (VB Only)

Visual Basic now enables you to omit unused and unwanted arguments from your event handlers. The thought is that this makes for code that reads more cleanly. In addition, it enables you to assign methods directly to event handlers without trying to determine the proper event signature.

For example, suppose you had the following code to respond to a button click event.

Private Sub Button1_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Button1.Click

  'your code here

End Sub

You could remove the arguments from this code (or never put them in). Your new code functions the same and looks like this.

Private Sub Button1_Click() Handles Button1.Click
  'your code here
End Sub

Creating an Automatically Implemented Property

C# and Visual Basic allow for a simplified property declaration called auto-implemented properties. With this feature, you can declare a property without having to declare a local private field to back the property. Instead, the compiler does this for you. This can be useful when you do not need logic inside the property’s assessors.

For example, suppose you want to define the property Name on the Employee class. You can declare this property without setting a private field variable, as shown here.

C#

public string Name { get; set; }

VB

Public Property Name As String

Notice that there is no logic in the get or set statements. Instead, the compiler creates an anonymous field to back the property for you.

Dropping the Underscore in VB for Line Continuation

Visual Basic added a feature in 2010 for implicit line continuation. This enables you to drop the need for the underscore (_) commonly used to indicate line continuation. For example, the following code shows a valid method signature without the need for the underscore required for line continuation.

Private Sub OnEmployeeUpdate(ByVal sender As Object,
  ByVal e As EmployeeUpdatedEventArgs)

There are many places in Visual Basic where you can eliminate the underscore and instead allow the compiler to use implicit continuation. These include after commas, after an open parenthesis, after an open curly brace, after concatenation, and more.

Working with Dynamic Languages/Objects

Most .NET development is about working with strongly typed objects where the compiler knows in advance the properties and methods that a given class exposes. However, there are objects (and languages) out there that do not have a static structure against which you can program. Instead, they are designed to get their information at runtime based on data inside an HTML form, a text file, XML, a database, or something similar. These objects and languages are said to be dynamic in that they get their structure only at runtime. Dynamic support was added to .NET for the purpose of simplifying the access to dynamic application programming interfaces (APIs) provided by languages such as IronPython and IronRuby or even those found in Office Automation.

The Dynamic Data Type

The C# language has a new data type called dynamic. This type is similar to object in that it might contain any actual type. In fact, in Visual Basic you simply use object to get dynamic-like behavior. The difference in C#, however, is that any value defined as dynamic only has its actual type inferred at runtime (and not at compile time). This means you do not have type checking against valid methods. That is, the compiler does not stop you from writing code against methods or properties it cannot see at design time. Instead, type checking is only done when the code executes. Of course, this means that your dynamic type should be the right type at the right time or you get errors.

You can define dynamic fields, properties, variable, or return types of methods. For example, the following shows a property defined as a dynamic.

public dynamic DyProperty { get; set; }

At first glance, it would seem that the dynamic keyword simply makes the type behave like types declared as objects. In fact, the differences are so slight that Visual Basic combines the concept of object and dynamic. However, in C#, the keyword dynamic indicates that the property can contain any value and that no type checking is done at compile time regardless of what the code looks like that uses the property. That is in contrast to types declared as objects, in which the compiler evaluates expressions that use the type and prevents certain code (such as doing arithmetic with objects). Dynamic types do not get this scrutiny by the compiler and therefore either execute properly or throw an error if a problem exists.

Dynamics are useful for dealing with types and code outside of .NET, such as IronPython. However, you have to be careful when using them for your own needs. Because no resolution is done until runtime, you do not get strong type checking by the compiler or with IntelliSense. There is also a slight memory and performance penalty to pay at runtime for dynamic objects.

Figure 3.4 shows an example of the experience inside Visual Studio. There are two methods here. The first, VarTestMethod, uses the var statement to create an instance of the Employee class. Notice an attempt to call the nonexistent property NewName is type-checked as an error. The second method, DynTestMethod, declares a dynamic instance of Employee. In this case, a call to emp.NewName does not get a compile time error. The compiler allows this call, but an exception will be thrown at runtime if you get it wrong.

FIGURE 3.4

FIGURE 3.4 Using dynamics means no type checking even in IntelliSense.

Creating a Custom Dynamic Object

A dynamic object is one that gets its type information for things such as properties and methods at runtime. This is typically due to the fact that the object is meant to represent dynamic information such as that contained in an HTML or XML script file. In both cases, the underlying HTML and XML files you create are unique to your needs. Therefore, you cannot code directly against these models. Instead, you often have to code against static objects and write syntax such as MyXml.GetElement("EmployeeId"). In this example, the GetElement method then searches for the given XML element and returns the same. With a dynamic object, the object can be written to interrogate your XML (or similar data) and enables developers to code against the dynamic object as if it contained the EmployeeId property. For example, developers could use your dynamic object to write their code as MyXml.EmployeeId. The dynamic object still has to interrogate the underlying structure for an EmployeeId, but this does simplify the coding for those working with your object and a dynamic structure such as XML or HTML.

You can create dynamic objects using either Visual Basic or C#. To do so, you inherit from the DynamicObject class inside the System.Dynamic namespace. You then override the members inside this class. These members serve as the basis for your dynamic items. For example, you can override the TrySetMember and TryGetMember to indicate the code that should be run when a user attempts to set or get a dynamic property on your object (such as calling MyXml.EmployeeId). In this case, if a user is trying to return a dynamic property, the TryGetMember method is called. Your code then determines how to return information for the dynamic property. (You might interrogate a file, for instance.)

There are many members on DynamicObject for which you can provide functionality. In addition to the two aforementioned members, the other notables include TryInvokeMember for invoking dynamic methods and TryCreateInstance for creating new instances of a dynamic object.

You might also add your own methods and properties to a dynamic object. In this case, the dynamic object first looks for your property or method before calling out to the appropriate Try member.

Let’s look at an example. Suppose that you were to write a dynamic object to represent an Employee. In this case, perhaps you get data scraped from a web page or inside an XML file. You therefore want to convert this data to an object for easier programming. In this case, you can create a new class called Employee and make sure it inherits from DynamicObject. In our example, we use a simple Hashtable of key value pairs to simulate the employee data. When a user creates an instance of this class, he is expected to pass the employee data to the dynamic class in the constructor. The skeleton of this class might then look like this.

C#

class Employee : System.Dynamic.DynamicObject
{
  Hashtable _memberData;

  public Employee(Hashtable employeeData)
  {
    _memberData = employeeData;
  }
}

VB

Public Class Employee
  Inherits System.Dynamic.DynamicObject

  Dim _memberData As Hashtable

  Public Sub New(ByVal employeeData As Hashtable)
    _memberData = employeeData
  End Sub

End Class

The next step is to override one or more of the Try members of DynamicObject to add our own functionality. In this simple example, we override the TryGetMember method to provide functionality for reading a property and add it to the Employee class created earlier. This method takes two parameters: binder and result. The binder parameter is an object that represents the dynamic call made to your object (such as its name). The result parameter is an outbound parameter of type object. You use it to pass back any value you intend to pass as the property read. Finally, the method returns a bool. This indicates true if the member was determined to exist; otherwise, you return false.

In the example, we simply look inside the Hashtable for a given key (based on the binder.Name property). If it exists, we set the result to its value and return true. Otherwise, we set the result to null and return false. The following shows the code for this additional member of our Employee class (assumes you’re using [imports in VB] System.Dynamic).

C#

public override bool TryGetMember(
  GetMemberBinder binder, out object result)
{
  if (_memberData.ContainsKey(binder.Name))
  {
    //set the out parameter results to the value in the
    //  hash table for the given key
    result = _memberData[binder.Name];

    //indicate that member existed
    return true;
  }
  else
  {
    //property does not exist in hash table
    result = null;
    return false;
  }
}

VB

Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder,
  ByRef result As Object) As Boolean

  If _memberData.ContainsKey(binder.Name) Then
    'set the out parameter results to the value in the
    '  hash table for the given key
    result = _memberData(binder.Name)

    'indicate that member existed
    Return True
  Else
    'property does not exist in hash table
    result = Nothing
    Return False
  End If

End Function

Using the Dynamic Object

You use a dynamic object like you would any other. You can create an instance, call methods and properties, and so on. However, you do not get type checking by the compiler. Again, this is because the object is late bound at runtime. In C#, you indicate a late-bound dynamic object using the keyword dynamic. In Visual Basic, you simply declare your type as object. Visual Basic figures out whether you are using late binding.

For example, suppose that you want to use the dynamic version of the Employee class created in the previous section. Recall that this class simulates converting data into an object. In this case, the simulation is handled through a Hashtable. Therefore, you need to declare an instance of the Employee class as dynamic (or object in VB) and then create an instance passing in a valid Hashtable. You can then call late-bound properties against your object. Recall that these properties are evaluated inside the TryGetMember method you overrode in the previous example. The following shows a Console application that calls the dynamic Employee object.

C#

class Program
{
  static void Main(string[] args)
  {
    Hashtable empData = new Hashtable();
    empData.Add("Name", "Dave Elper");
    empData.Add("Salary", 75000);
    empData.Add("Title", "Developer");

    dynamic dyEmp = new Employee(empData);

    Console.WriteLine(dyEmp.Name);
    Console.WriteLine(dyEmp.Salary);
    Console.WriteLine(dyEmp.Title);
    Console.WriteLine(dyEmp.Status);

    Console.ReadLine();
  }
}

VB

Module Module1

  Sub Main()

    Dim empData As New Hashtable()
    empData.Add("Name", "Dave Elper")
    empData.Add("Salary", 75000)
    empData.Add("Title", "Developer")

    Dim dyEmp As Object = New Employee(empData)

    Console.WriteLine(dyEmp.Name)
    Console.WriteLine(dyEmp.Salary)
    Console.WriteLine(dyEmp.Title)
    Console.WriteLine(dyEmp.Status)

    Console.ReadLine()

  End Sub

End Module

All this code passes the compiler’s test and executes accordingly. However, the last call to dyEmp.Status is not valid. In this case, the dynamic object returns false and thus throws an error. Figure 3.5 shows the results, including the Console output and the error message trying to access a bad member.

FIGURE 3.5

FIGURE 3.5 The dynamic object executing in the Console and throwing an error in Visual Studio.

Covariance and Contravariance

The .NET languages support the concepts of covariance and contravariance. These concepts enable you to reduce restrictions on strong typing when working with delegates, generics, or generic collections of objects. In certain situations, decreasing the type restrictions might increase your ability to reuse code and objects and decrease the need to do a lot of casting or converting to provide the right type to a method.

Covariance is the ability to use a more derived type than that which was originally specified by an interface or function signature. For example, you could assign a list of strings to a generic list that only takes objects if that list supports covariance (because strings inherit from objects and are thus more derived). Contravariance is similar; it is the ability to use a less-derived type for a given parameter or return value. That is, you might assign an object type as the return type for a method that returns a string (provided that method supports contravariance).

It is important to note that the target type has to support covariance or contravariance. This is not a change to the entire language. Instead, it introduces a couple new keywords to allow support for these concepts when appropriate.

Variance in Generic Collections

Many of the generic interfaces in the latest version of the .NET Framework now support variance. This includes the interfaces IEnumerable<T> and IEnumerator<T> (among others) that support covariance. This means you can have support for variance inside your collections.

For example, you might have a list of Manager objects. Recall that Manager derives from Employee. Therefore, if you need to work with the Manager list as an Employee collection, you can do so using List and the IEnumerable interface. The following code shows an example.

C#

IEnumerable<Manager> managers = new List<Manager>();
IEnumerable<Employee> employees = managers;

VB

Dim managers As IEnumerable(Of Manager) = New List(Of Manager)()
Dim employees As IEnumerable(Of Employee) = managers

The preceding code compiles and executes because Manager inherits from Employee and is thus more derived. Using covariance, you can use a list of Manager objects with a list of Employee objects. For example, you might have a method that takes a list of Employee objects as a parameter. Using covariance support, you can pass the Manager list instead.

Additional Considerations

Support for variance has additional ramifications for your coding. These include the following:

  • Custom generic classes—If you create your own custom generic classes, you can declare support for variance. You do so at the interface level using the out (covariant) and in (contravariant) keywords on generic type parameters.
  • Delegate variance—Using variance, you can assign methods to delegates that return more derived types (covariance). You can also assign those methods that accept parameters that have a less derived type (contravariance).
  • Func and Action—The generic delegates Func<> and Action<> now support variance. This enables you to more easily use these delegates with other types (and thus increase the flexibility of your code).

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020