March 2007 - Posts

Developer & IT Pro Days 2007 has come to an end. This year's edition in Ghent was in my opinion the best edition so far; congratulations to the entire organization: Tom, Ritchie, David, Arlindo, Wim and many others who made it a great success once more. I think the technical curiosity of the audience has been satisfied a bunch; let's make it even better next year!

For me, it was a quite busy week too. Yesterday, I delivered my talk on IIS 7.0 for developers, focusing on the most exciting features in the next-generation web application server platform from Microsoft. More specifically, we focused on extensibility using managed code (handlers and modules), the configuration system, the new IIS Manager and a bit of diagnostics and security. I'll post demo scripts and sample code later this week or beginning of next week, so stay tuned!

But Dev & IT Pro Days 2007 was also an important milestone for me. On the conference I made it official: starting October this year, I'll join the Microsoft Corporation headquarters in Redmond, WA to start to work on the WPF team, taking on a role as SDE in Building 10. I'll talk about the whole story later in a separate blog post.

PS: No worries, I'll keep blogging about exciting topics over here for the next couple of months!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Only two weeks left for this year's Belux Developer and IT Pro Days 2007. If you haven't registered yet, here are some good reasons to do so:

  • Two preconferences on March 27, covering Microsoft Virtualization and The Microsoft Web Story.
  • A total of 70 breakout sessions delivered by top speakers on March 28 and March 29.
  • Keynote delivered by David Chappell.
  • An end-to-end track on architecture by Ron Jacobs.
  • Meet your Developer and IT Pro peers to network (already over 1000 (!) attendees registered).
  • Visug geek bowling at the Overpoort, Ghent on Wednesday March 28. Register via www.visug.be.

Oh almost forget to mention my own covering Internet Information Services (IIS) 7.0: End-to-End Overview of Microsoft's New Application Web Server on 29 March 2007 at 9 AM (rescheduled!).

A few quick links of the organizers:

Don't hesitate any longer; this is your chance to stay tuned. Register now!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

The latest Service Pack for Windows Server 2003 and Windows XP Pro x64, SP2, is ready to be downloaded from http://www.microsoft.com/technet/windowsserver/sp2.mspx. There a lots of enhancements that also have impact on developers:

  • XmlLite is a library that allows developers to write XML-driven apps that perform faster than the current MSXML/DOM and MSXML/SAX2 implementations.
  • MMC 3.0 is the next generation of the Microsoft Management Console technology, enabling managed code snap-ins explained on my blog previously (MMC 3.0 - A managed code 'task manager' MMC 3.0 snap-in). It's possible to use Windows Forms to be hosted inside a snap-in; the snap-in model is richer than before; there's the new actions pane; asynchronous execution increases reliability and user experience; etc.
  • ICALCS will be your next big friend if you deal with ACLs at lot. It's already in Vista.
  • SQL Server 2005 SP2 performance improvements.

A more complete overview is available over here.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

To the Belgian developer (academic) audience: save the date. I'll deliver a (Dutch-spoken) session on .NET 3.0 at the Erasmushogeschool in Brussels on April 16, 2007 between 1 PM and 4 PM. More information can be found on Walter Stiers's Blog.

Topics covered during the session will be WPF, WCF, WF and WCS as well as a short sneak preview of "Orcas" (.NET 3.5 and Visual Studio tools).

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

You can find the original quiz over here. There have been lots of great answers, thanks to all readers! The bottom line however is that one should be careful when doing performance optimizations; in much cases the code doesn't become cleaner (even the opposite). One shouldn't trade design guidelines for speed unless there are very very good reasons to do so. An example includes trading a class for a struct (see Framework Design Guidelines for a full elaboration on these topics).

Below you can find a piece of code that illustrates a few performance optimizations. General recommendations include:

  1. Avoid the use of accessor functions like properties and structs if you can get to the data directly (without breaking encapsulation however).
  2. Calculations sometimes benefit from an accumulator variable, as illustrated below.
  3. Jagged arrays outperform multidimensional arrays because of a variety of reasons. See Performance Tips and Tricks in .NET Applications for more info; this article dates from the v1 ages of .NET but most guidelines still hold.

The code:

1 using System; 2 using System.Threading; 3 using System.Text; 4 using System.Diagnostics; 5 6 class Matrix 7 { 8 private double[,] m; 9 internal double[][] mj; 10 11 public Matrix(int dim0, int dim1) 12 { 13 m = new double[dim0,dim1]; 14 mj = new double[dim0][]; 15 for (int i = 0; i < dim0; i++) 16 mj[i] = new double[dim1]; 17 } 18 19 public int Height { get { return m.GetLength(0); } } 20 public int Width { get { return m.GetLength(1); } } 21 22 public double this[int x, int y] 23 { 24 get { return m[x,y]; } 25 set { m[x,y] = value; } //hasn't changed to set mj value - avoid perf influence 26 } 27 28 public static int algo = 0; 29 public static string desc; 30 31 public static Matrix operator*(Matrix m1, Matrix m2) 32 { 33 if (m1.Width != m2.Height) 34 throw new InvalidOperationException("Matrices should have compatible dimensions for multiplication."); 35 36 switch (algo) 37 { 38 case 1: 39 { 40 desc = "Avoid properties"; 41 int h = m1.Height; 42 int w = m2.Width; 43 int l = m1.Width; 44 Matrix m = new Matrix(h, w); 45 for(int i = 0; i < h; i++) 46 { 47 for(int j = 0; j < w; j++) 48 { 49 m[i,j] = 0; 50 for (int k = 0; k < l; k++) 51 m[i,j] += m1[i,k] * m2[k,j]; 52 } 53 } 54 return m; 55 } 56 57 case 2: 58 { 59 desc = "Avoid indexers"; 60 int h = m1.Height; 61 int w = m2.Width; 62 int l = m1.Width; 63 Matrix m = new Matrix(h, w); 64 for(int i = 0; i < h; i++) 65 { 66 for(int j = 0; j < w; j++) 67 { 68 m.m[i,j] = 0; 69 for (int k = 0; k < l; k++) 70 m.m[i,j] += m1.m[i,k] * m2.m[k,j]; 71 } 72 } 73 return m; 74 } 75 76 case 3: 77 { 78 desc = "Use accumulator variable"; 79 int h = m1.Height; 80 int w = m2.Width; 81 int l = m1.Width; 82 Matrix m = new Matrix(h, w); 83 for(int i = 0; i < h; i++) 84 { 85 for(int j = 0; j < w; j++) 86 { 87 double res = 0; 88 for (int k = 0; k < l; k++) 89 res += m1.m[i,k] * m2.m[k,j]; 90 m.m[i,j] = res; 91 } 92 } 93 return m; 94 } 95 96 case 4: 97 { 98 desc = "Use jagged arrays"; 99 int h = m1.Height; 100 int w = m2.Width; 101 int l = m1.Width; 102 Matrix m = new Matrix(h, w); 103 for(int i = 0; i < h; i++) 104 { 105 for(int j = 0; j < w; j++) 106 { 107 double res = 0; 108 for (int k = 0; k < l; k++) 109 res += m1.mj[i][k] * m2.mj[k][j]; 110 m.mj[i][j] = res; 111 } 112 } 113 return m; 114 } 115 116 case 5: 117 { 118 desc = "Go unsafe with multidimensional"; 119 int h = m1.Height; 120 int w = m2.Width; 121 int l = m1.Width; 122 Matrix m = new Matrix(h, w); 123 unsafe 124 { 125 fixed (double* pm = m.m, pm1 = m1.m, pm2 = m2.m) 126 { 127 int i1, i2; 128 for(int i = 0; i < h; i++) 129 { 130 i1 = i * l; 131 for(int j = 0; j < w; j++) 132 { 133 i2 = j; 134 double res = 0; 135 for (int k = 0; k < l; k++, i2 += w) 136 res += pm1[i1 + k] * pm2[i2]; 137 pm[i * w + j] = res; 138 } 139 } 140 } 141 } 142 return m; 143 } 144 145 default: 146 { 147 desc = "Naive"; 148 Matrix m = new Matrix(m1.Height, m2.Width); 149 for(int i = 0; i < m.Height; i++) 150 { 151 for(int j = 0; j < m.Width; j++) 152 { 153 m[i,j] = 0; 154 for (int k = 0; k < m1.Width; k++) 155 m[i,j] += m1[i,k] * m2[k,j]; 156 } 157 } 158 return m; 159 } 160 } 161 } 162 } 163 164 class Program 165 { 166 static void Main() 167 { 168 Random rand = new Random(); 169 Stopwatch sw = new Stopwatch(); 170 TimeSpan original = TimeSpan.FromSeconds(0); 171 172 Matrix m1 = new Matrix(20,30); 173 for (int i = 0; i < m1.Height; i++) 174 for (int j = 0; j < m1.Width; j++) 175 m1[i,j] = rand.Next(-100,100); 176 177 Matrix m2 = new Matrix(30,40); 178 for (int i = 0; i < m2.Height; i++) 179 for (int j = 0; j < m2.Width; j++) 180 m2[i,j] = rand.Next(-100,100); 181 182 Matrix ro = null; 183 184 { 185 sw.Start(); 186 for (int k = 0; k < 10000; k++) 187 ro = m1 * m2; 188 sw.Stop(); 189 original = sw.Elapsed; 190 Console.WriteLine("Original - {0}", original); 191 } 192 193 { 194 for (Matrix.algo = 1; Matrix.algo <= 5; Matrix.algo++) 195 { 196 Matrix r = null; 197 sw.Reset(); 198 sw.Start(); 199 for (int k = 0; k < 10000; k++) 200 r = m1 * m2; 201 sw.Stop(); 202 Console.WriteLine("Algo {0} - {1} {3} ({2:#.00}x)", Matrix.algo, sw.Elapsed, ((double)original.TotalMilliseconds) / sw.Elapsed.TotalMilliseconds, Matrix.desc); 203 } 204 } 205 } 206 }

Needless to say, performance figures will vary from machine to machine and on every execution. Over here the figures look roughly like this:

Original - 00:00:07.9269539
Algo 1   - 00:00:03.0053351 Avoid properties (2,64x)
Algo 2   - 00:00:02.5814237 Avoid indexers (3,07x)
Algo 3   - 00:00:02.4030079 Use accumulator variable (3,30x)
Algo 4   - 00:00:01.5665534 Use jagged arrays (5,06x)
Algo 5   - 00:00:01.0873534 Go unsafe with multidimensional (7,29x)

The last optimization is likely the most risky one since it uses unsafe code to get rid of bounds checking overhead. Unless you really know what you're doing, it's better to avoid unsafe code anyhow (a speed-up of a factor 5 just by applying managed code optimizations is great already!).

Have fun!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Time for another security feature in Windows Vista: WinSta0 isolation. The first question that might pop up in your head is "So, what exactly is WinSta0?". Keith Brown has the answer.

The problem with WinSta0 is the possibility for Windows Services to display a UI prompt in the window station. Pre-Vista, this prompt just appears on top of the desktop of the user logged on to the system. Because of this, the service is exceeding its boundary of isolation and the inware user is providing information across that "trust boundary".

Windows Vista reduces this risk by isolating WinSta0 from the active user's desktop; a user has to provide his/her consent to switch to the bare WinSta0 when that screams for attention (which is detected by an executable called UI0Detect.exe as displayed below):

When you see this kind of message, the app you're dealing with has "partial incompatibility with Windows Vista". The message to developers: fix it - Windows doesn't like the old approach anymore! An example I've been faced with in practice is the HP LaserJet 1020 software that uses WinSta0 to show out of paper messages and other printer maintenance messages.

In this post, I'm showing you the code for a demo of a WinSta0 isolation demo I gave some time ago, together with demonstration instructions.

 

Step 1 - Creating an interactive Windows Service

So, for sake of the demo, let's create something we really shouldn't have created: an interactive Windows Service. Open Visual Studio 2005 and create a new Windows Service project called "WinSta0Inspector" in C#:

Next, go to Service1.cs and change the service name to WinSta0Inspector:

Right-click the designer surface and choose Add Installer. This will create a new file ProjectInstaller.cs that makes the executable installutil.exe-able. In there, select the serviceProcessInstaller1 "control" and set the Account property to Local System (to make things really bad):

In order to make the service interactive, we'll create a simple Windows Form. Right click the project in the Solution Explorer, choose Add New Item and add a Windows Form called ExecuteCommand.cs. Design it so that it looks like this:

Hook up event handlers for both LinkLabels, with definitions like this:

private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { Process.Start("cmd.exe"); } private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { DialogResult = DialogResult.OK; }

Don't forget to import System.Diagnostics in order to have access to the Process class. Now go back to Service1.cs and switch to the code view. Define the Service1 class as follows:

public partial class Service1 : ServiceBase { private ExecuteCommand dialog = new ExecuteCommand(); public Service1() { InitializeComponent(); } protected override void OnStart(string[] args) { } protected override void OnCustomCommand(int command) { MessageBox.Show("Welcome to WinSta0.", "WinSta0 is calling you!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); dialog.ShowDialog(); } }

You'll need to import System.Windows.Forms to have access to MessageBox. This completes our interactive service.

 

Step 2 - Installing it

Compile the solution and switch to a Visual Studio 2005 Command Prompt which runs elevated with administrator privileges. Install the service using installutil -i WinSta0Inspector.exe. Then open the Services MMC snap-in (services.msc), locate the WinSta0Inspector service and change its properties to make it interactive:

Command-line freaks could also use the following command to install the service directly as an interactive service; no need to run installtutil then, just do this:

sc create WinSta0Inspector binPath= WinSta0Inspector.exe type= interact DisplayName= "WinSta0 Inspector"

 

Step 3 - Action!

In order to see it in action, start the service and send it a custom command. You could write another app to send the custom command using the System.Service.ServiceController::ExecuteCommand method, but sc.exe has everything we need:

First, we started the service using net start WinSta0Inspector. Next, we sent a command to the service by using sc control WinSta0Inspector 129 (valid commands should be higher than 128, other values are system-reserved). Right away you'll see the Interactive services dialog detection dialog popping up in the background:

Click Show me the message and Vista will bring you to the raw WinSta0 environment which should look somewhat like this:

Press OK in our message box; the WinForms dialog will show up now:

Feel free to take a look around WinSta0 using the command-prompt link on the form. For example, run whoami /all to find out about your SYSTEM power :-)

If you have a hacker's mindset you might find HKLM\SECURITY\SAM an attractive place to visit while you're the Windows ├╝bermensch :o. Below you can see a few other screenshots of what it is like to be in WinSta0 (e.g. how did I create the screenshots?):

 

Have fun!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Note: This article applies to the Visual Studio "Orcas" March 07 CTP build that can be downloaded from the Microsoft website.

In this post we'll talk about embedding UAC (User Account Control) manifests in managed code executables without having to rely on rather dirty tricks, as I explained in my older blog post entitled Windows Vista - Demand UAC elevation for an application by adding a manifest using mt.exe. Please read this post prior to reading this post, in order to get a good idea about UAC and manifests.

Essentially, this article performs a little word substitution on the title of the previous post: adding a manifest using mt.exe using Visual Studio "Orcas".

 

Step 1 - Creating the Windows Forms app

We'll start by creating the same app as we did in the previous post mentioned above. Open up VS "Orcas", create a new (C#) Windows Forms project called UacDemo and add a label called "label1" to the designer surface. As a side-note, play around with the IDE designer for Windows Forms a bit, you'll see that the Layout toolbar has been revamped (for example, add another label, select both labels and observe the options in this toolbar):

Next, go to the code and add an event handler for Form1_Load that does the following:

label1.Text = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) ? "Yup" : "Nope";

You'll need to bring the System.Security.Principal namespace in scope in order to compile the code above.

 

Step 2 - Add a manifest to the project

Add a new item to the project and choose for an XML file. Call it UacDemo.exe.manifest:

Add the following contents to it:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="UacDemo" type="win32"/> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator"/> </requestedPrivileges> </security> </trustInfo> </assembly>

Notice that the name attribute of the assemblyIdentity element has to be set to the name of the executable (without .exe extension). Information about UAC manifests can be found by searching "requestedPrivileges" on the internet for instance.

 

Step 3 - Tell the build environment to include the manifest

This is where things got difficult in the past. A solution pre-Orcas was explained on my aforementioned previous blog entry on UAC using a post-build step calling the mt.exe tool. Now, things have become much more simple. Just go to the properties of the project, tab Application and scroll down to the section "Resources" as shown below:

In the manifest dropdown box you can now point to the manifest we've created in the previous step.

 

Step 4 - Build, inspect, test

That's it; we're done now! To illustrate what has happened, take a look at the .csproj file of the project using an XML editor; below is a snippet from this file on my machine:

<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProductVersion>9.0.20209</ProductVersion> <SchemaVersion>2.0</SchemaVersion> <ProjectGuid>{8FB484D6-73DB-4AEE-8222-7FE5B0CBD622}</ProjectGuid> <OutputType>WinExe</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>UacDemo</RootNamespace> <AssemblyName>UacDemo</AssemblyName> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <ApplicationManifest>UacDemo.exe.manifest</ApplicationManifest> </PropertyGroup>

First, observe the presence of multi-targeting: the Project's ToolsVersion attribute tells the tools that use the .csproj file to use the 3.5 version of the framework; furthermore, TargetFrameworkVersion tags appear in the file, as well as RequiredTargetFramework tags for dependencies (not illustrated in the snippet above). However, for us the ApplicationManifest element is much more interesting since it points to our manifest file; this tells MSBuild to take mt.exe alike steps to include the manifest in the executable when compilation has been done successfully.

To see that the manifest is included correctly, you can go to the Visual Studio Orcas Command Prompt, cd into the bin\Debug or bin\Release folder of the UacDemo project and call mt -inputresource:uacdemo.exe -out:uacdemo.exe.manifest to extract the manifest from the file and to see what's in there:

Finally, run the file on your Vista machine and you should see the UAC prompt popping up to elevate the privileges of the app:

Notice that creating screenshots from the UAC prompt isn't easy due to the presence of the "UAC Secure Desktop" where the UAC prompts are displayed. In order to make it possible, open up secpol.msc, go to Local Policies, Security Options and tweak the "User Account Control: Switch to the secure desktop when prompting for elevation" setting to Disabled (don't forget to revert it afterwards!):

Enjoy!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

A new IDE, a new look-n-feel. Compared to the VS 6 ages, VS has come a long way. I remember a quote from one of my colleague community members in Belgium when VS2005 was released: "This is an environment I really do like to work in; VS.NET wasn't...". Yes, even for geeky (long-haired cold pizza eating and coke drinking) developers (in dark offices) - sorry for the clich├ęs - IDE style matters. An Microsoft has done it again in VS Orcas; overexposure to the new March 07 CTP might cause that you never want to go back or leave the VPC :-). A few screenshots will clarify this statement:

(So, you really thought the thumbnail API in Vista was cool? Or the thumbnails of opened tabs in IE7? Explore Orcas's CTRL-TAB functionality!)

(Menus in Orcas style with rounded borders.)

(Next generation web designer with split view, CSS support and more - a shared component with Office SharePoint Designer.)

Note: The web designer deserves much more attention than a few screenshots in this post; if I find the time to rejuice my web dev passion I'll blog about it for sure!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

I was just creating some screenshots on Code Metrics in Orcas when I saw the fxcop feed getting bold :-). So here's one of the rare metablogging posts you'll find on my blog: Announcing Visual Studio Code Metrics!

This is how it looks over here for the LINQSQO project I'm updating for the March 07 CTP (more info to follow later):

Glad to see everything is going green :-).

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

A few months ago in the TechEd: Developers Europe timeframe I blogged about the Automatic Property feature in C# 3.0. Today I'm finally able to give you a little demo on it via the Orcas March 07 CTP. In case you missed it, tons of people have blogged about the availability of the March 07 CTP, including David.

So here's the code sample I'm talking about:

1 using System; 2 3 namespace ConsoleApplication1 4 { 5 class Program 6 { 7 public string Name { get; set; } 8 9 static void Main(string[] args) 10 { 11 var p = new Program(); 12 p.Name = "Bart"; 13 } 14 } 15 }

The clue is on line 7 where I've declared an automatic property. Basically this frees me from the burden of declaring a private variable and a get and set accessor to it by means of a property. Although you can just use the "prop" code snippet in Visual Studio, this is much cleaner in case you don't need the private fields at all. Imagine some entity mapping class that consists of 20 properties, do you want to see all of the private variable, getter and setter noise around your class? I don't think so.

Please notice that the use of automatic properties is not equal to just defining a public field - you still keep the get_PropertyName and set_PropertyName methods behind the scenes as well as all the metadata that goes with a property, as illustrated below:

This means your code can be upgraded at any time to define getters/setters together with an explicitly defined member variable when you need to do so, without having to recompile external consumers of your code (i.e. the "contract" remains untouched). Behind the scenes what happens is the injection of a private member variable, prefixed with <>k__AutomaticallyGeneratedPropertyField#, like this:

.field private string '<>k__AutomaticallyGeneratedPropertyField0' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

The CompilerGeneratedAttribute attribute is useful for tools to find out about these auto-generated things. Next, the compiler emits a getter and setter for you:

.method public hidebysig specialname instance string get_Name() cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Code size 11 (0xb) .maxstack 1 .locals init (string V_0) IL_0000: ldarg.0 IL_0001: ldfld string ConsoleApplication1.Program::'<>k__AutomaticallyGeneratedPropertyField0' IL_0006: stloc.0 IL_0007: br.s IL_0009 IL_0009: ldloc.0 IL_000a: ret } // end of method Program::get_Name .method public hidebysig specialname instance void set_Name(string 'value') cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld string ConsoleApplication1.Program::'<>k__AutomaticallyGeneratedPropertyField0' IL_0007: ret } // end of method Program::set_Name

All the stuff in here is pretty much the same as a manually defined property, except for the presence of the CompilerGeneratedAttribute attribute. Finally, there's the metadata for the property:

.property instance string Name() { .get instance string ConsoleApplication1.Program::get_Name() .set instance void ConsoleApplication1.Program::set_Name(string) } // end of property Program::Name

Notice that automatic properties should have both a getter and a setter declared. Read-only or write-only properties are not permitted. After all, a typical use for automatic properties is to define "property bag" kind of classes or structs. Of course, there's IntelliSense too:

With the screenshot above I've also shown that local type inference (keyword "var") has now full IDE support (i.e. when you type p. the IDE already knows the type of the variable in order to provide accurate IntelliSense). Enjoy!

Technorati tags: , ,
Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

More Posts