Archive for May, 2007

const string vs. static string

Sunday, May 20th, 2007

There was an email thread going around the office not too long ago debating whether it is more efficient to use string constants or static readonly strings (in C#). An example was given that seemed to indicate that the static strings were better:

public class Sample { static readonly string StaticString = "StaticString"; const string ConstString = "ConstString"; public void SetValues(StringCollection strings) { strings[StaticString] = "foo"; strings[ConstString] = "bar"; } public void GetValues(StringCollection strings) { string value1 = strings[StaticString]; string value2 = strings[ConstString]; } }

The disassembly of that code shows something surprising:

public class Sample { // Fields private const string ConstString = "ConstString"; private static readonly string StaticString = "StaticString"; // Methods public void GetValues(NameValueCollection values) { string text1 = values[StaticString]; string text2 = values["ConstString"]; } public void SetValues(NameValueCollection values) { values[StaticString] = "foo"; values["ConstString"] = "bar"; } }

It doesn't look like the const string is being used at all, while it's obvious that the static string is. This can't be right. Let's take a look at the IL:

.class public auto ansi beforefieldinit Sample extends [mscorlib]System.Object { .method private hidebysig specialname rtspecialname static void .cctor() cil managed { .maxstack 8 L_0000: ldstr "StaticString" L_0005: stsfld string TestStringConstantsLibrary.Sample::StaticString L_000a: ret } .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: call instance void [mscorlib]System.Object::.ctor() L_0006: ret } .method public hidebysig instance void GetValues(class [System]System.Collections.Specialized.NameValueCollection values) cil managed { .maxstack 8 L_0000: ldarg.1 L_0001: ldsfld string TestStringConstantsLibrary.Sample::StaticString L_0006: callvirt instance string [System]System.Collections.Specialized.NameValueCollection::get_Item(string) L_000b: pop L_000c: ldarg.1 L_000d: ldstr "ConstString" L_0012: callvirt instance string [System]System.Collections.Specialized.NameValueCollection::get_Item(string) L_0017: pop L_0018: ret } .method public hidebysig instance void SetValues(class [System]System.Collections.Specialized.NameValueCollection values) cil managed { .maxstack 8 L_0000: ldarg.1 L_0001: ldsfld string TestStringConstantsLibrary.Sample::StaticString L_0006: ldstr "foo" L_000b: callvirt instance void [System]System.Collections.Specialized.NameValueCollection::set_Item(string, string) L_0010: ldarg.1 L_0011: ldstr "ConstString" L_0016: ldstr "bar" L_001b: callvirt instance void [System]System.Collections.Specialized.NameValueCollection::set_Item(string, string) L_0020: ret } .field private static literal string ConstString = string('ConstString') .field private static initonly string StaticString }

Same odd result, though we can see that the const string is defined as a static field (with the literal modifier instead of the initonly modifier). What gives? I refuse to believe that const strings are duplicated all over the assembly. Opening the assembly in a hex editor (I used Visual Studio's Binary Editor), you can see that there are only 2 instances of the string (not the 3 you would expect):

In fact, if we add the following method to our class:

public void WriteConsole() { Console.WriteLine("ConstString"); Console.WriteLine("ConstString"); Console.WriteLine("ConstString"); Console.WriteLine("ConstString"); Console.WriteLine("ConstString"); Console.WriteLine("ConstString"); Console.WriteLine("ConstString"); }

We can see that it does not add any additional strings to our assembly. There are still only the 2 instances:

So, const string is not as bad as it initially looks. I am curious why there are 2 instances of the string though, and why the field isn’t referenced…

Getting the name of a builtin Windows security object

Tuesday, May 15th, 2007

It’s not obvious how to do this in .NET, but it’s actually pretty easy once you decipher the documentation.

The two classes you need are in the System.Security.Principal namespace:

You need to create a new instance of a SecurityIdentifier using the constructor that takes a WellKnownSidType enum value. The WellKnownSidType value specifies the built-in security object you are interested in.

// Assume using System.Security.Principal SecurityIdentifier si = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);

Given the instance of the SecurityIdentifier, you obtain the name via the Translate() method, passing typeof(NTAccount) and casting the result to an NTAccount instance.

NTAccount acct = (NTAccount)si.Translate(typeof(NTAccount));

The Value property of the NTAccount instance contains the name of the security group or user.

Console.WriteLine(acct.Value);

And for bonus points, here's a PowerShell function to do the same thing:

function GetWellKnownAccountName([string]$wellKnownSidType) { $si = new-object System.Security.Principal.SecurityIdentifier( [System.Security.Principal.WellKnownSidType]$wellKnownSidType, $null) $acct = $si.Translate([System.Security.Principal.NTAccount]) return $acct.Value }

Sample usage:

PS> GetWellKnownAccountName("BuiltinAdministratorsSid") BUILTIN\Administrators