String Concatenation in .NET – what really goes on?
There are a number of ways to concatenate a string in C# and other .NET languages. Is there a *best* way?
Let's look at how the C# compiler translates our code into IL to see the differences.
Here is a simple little console application to concatenate a string via String.Concat, String.Format, System.String's (+) operator, System.String's Join, and StringBuilder's Append method.
class Program
{
static void Main(string[] args)
{
string first = "The cake";
string second = " is a";
string third = " lie.";
string fromConcat = Concat(first, second, third);
string fromFormat = Format(first, second, third);
string fromPluses = Pluses(first, second, third);
string fromBuilder = Builder(first, second, third);
string fromJoiner = Joiner(first, second, third);
Console.WriteLine(fromConcat);
Console.WriteLine(fromFormat);
Console.WriteLine(fromPluses);
Console.WriteLine(fromBuilder);
Console.WriteLine(fromJoiner);
Console.ReadLine();
}
static string Concat(params string[] strings)
{
return String.Concat(strings);
}
static string Format(params string[] strings)
{
return string.Format("{0}{1}{2}", strings);
}
static string Pluses(params string[] strings)
{
return strings[0] + strings[1] + strings[2];
}
static string Builder(params string[] strings)
{
StringBuilder sb = new StringBuilder();
sb.Append(strings[0]);
sb.Append(strings[1]);
sb.Append(strings[2]);
return sb.ToString();
}
static string Joiner(params string[] strings)
{
return string.Join("", strings);
}
}
Which of these do you think will require the most intermediary code? I'd say it's StringBuilder, since we have to instantiate an object and call a method on that object a number of times. I'd like to go over these individually. The code in Main() works the same for all methods: a new string array is created and passed to the method which returns a string into a new variable. Here are the methods:
Concat
.method private hidebysig static string
Concat(string[] strings) cil managed
{
.param [1]
.custom instance void [mscorlib]System.ParamArrayAttribute::.ctor() = ( 01 00 00 00 ) // Code size 12 (0xc)
.maxstack 1
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call string [mscorlib]System.String::Concat(string[])
IL_0007: stloc.0
IL_0008: br.s IL_000a
IL_000a: ldloc.0
IL_000b: ret
} // end of method Program::Concat
The C# compiler converts String.Concat into the smallest amount of code, and (I assume), the best as far as performance. It couldn't get any simpler than this method: it loads the arguments, passes them to the method, stores that result in memory and returns.
Format
.method private hidebysig static string
Format(string[] strings) cil managed
{
.param [1]
.custom instance void [mscorlib]System.ParamArrayAttribute::.ctor() = ( 01 00 00 00 ) // Code size 17 (0x11)
.maxstack 2
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldstr "{0}{1}{2}"
IL_0006: ldarg.0
IL_0007: call string [mscorlib]System.String::Format(string, object[])
IL_000c: stloc.0
IL_000d: br.s IL_000f
IL_000f: ldloc.0
IL_0010: ret
} // end of method Program::Format
String.Format (my favorite of all the methods), comes in at a code size of 17 lines. The additional space is required for the string's template, which of course isn't necessary with String.Concat, but allows you to perform a number of useful operations on a string. For instance, String.Concat will only string the strings together. If you want to add a space between them, you'd have to create either one string to represent a space and concat that along (which gives you the same amount of code as String.Format anyway), or you could do something like:
string overDoingIt = String.Concat(string[0], " probably", string[1], " big fat", string[2]);
Not to mention, String.Format allows you to easily apply formatting rules without individually creating new objects. Granted, it would be converted into IL as something using a number formatter, but it makes our jobs as developers much easier.
Pluses
.method private hidebysig static string
Pluses(string[] strings) cil managed
{
.param [1]
.custom instance void [mscorlib]System.ParamArrayAttribute::.ctor() = ( 01 00 00 00 ) // Code size 20 (0x14)
.maxstack 4
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldc.i4.0
IL_0003: ldelem.ref
IL_0004: ldarg.0
IL_0005: ldc.i4.1
IL_0006: ldelem.ref
IL_0007: ldarg.0
IL_0008: ldc.i4.2
IL_0009: ldelem.ref
IL_000a: call string [mscorlib]System.String::Concat(string, string, string)
IL_000f: stloc.0
IL_0010: br.s IL_0012
IL_0012: ldloc.0
IL_0013: ret
} // end of method Program::Pluses
I see the (+) operator used a lot for string concatenation. There have been a number of articles that claim this performance is worse than that of String.Concat. Not even bothering with the actual performance aspect of it, you can see that this requires nearly twice as much intermediate language code to be generated than String.Concat.
If you're writing an application that requires thousands of string concatenation operations, I'd suggest using String.Concat. Even if you have to hard-code single characters as constants and occasionally pass a new string into the mix, it should still offer a great deal less generated code (and presumably better performance) than the (+) operator.
Builder
.method private hidebysig static string
Builder(string[] strings) cil managed
{
.param [1]
.custom instance void [mscorlib]System.ParamArrayAttribute::.ctor() = ( 01 00 00 00 ) // Code size 48 (0x30)
.maxstack 3
.locals init ([0] class [mscorlib]System.Text.StringBuilder sb, [1] string CS$1$0000)
IL_0000: nop
IL_0001: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldarg.0
IL_0009: ldc.i4.0
IL_000a: ldelem.ref
IL_000b: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0010: pop
IL_0011: ldloc.0
IL_0012: ldarg.0
IL_0013: ldc.i4.1
IL_0014: ldelem.ref
IL_0015: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_001a: pop
IL_001b: ldloc.0
IL_001c: ldarg.0
IL_001d: ldc.i4.2
IL_001e: ldelem.ref
IL_001f: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0024: pop
IL_0025: ldloc.0
IL_0026: callvirt instance string [mscorlib]System.Object::ToString()
IL_002b: stloc.1
IL_002c: br.s IL_002e
IL_002e: ldloc.1
IL_002f: ret
} // end of method Program::Builder
I see this used a lot in ASP.NET for injecting startup JavaScript scripts (even for 5 lines of code!). Aside from the obvious problems with that, I'd like to mention that in .NET 3.0 and higher, Page.ClientScript.RegisterStartupScript now has an overload which adds script tags. That means you don't have to worry about the curly braces that would otherwise mess up your String.Format.
Anyway, what's going on here is: in lines 15, 21, and 27, we're calling the method Append on our instance of StringBuilder (instantiated as 'sb' in line 7). The strings are popped off the stack and passed into the method. At the end, we still have to call ToString() on our object. This is obviously way more work than is necessary. Granted, there are times when StringBuilder comes in handy, but simple concatenation really shouldn't use it.
Joiner
.method private hidebysig static string
Joiner(string[] strings) cil managed
{
.param [1]
.custom instance void [mscorlib]System.ParamArrayAttribute::.ctor() = ( 01 00 00 00 ) // Code size 17 (0x11)
.maxstack 2
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldstr ""
IL_0006: ldarg.0
IL_0007: call string [mscorlib]System.String::Join(string, string[])
IL_000c: stloc.0
IL_000d: br.s IL_000f
IL_000f: ldloc.0
IL_0010: ret
} // end of method Program::Joiner
String.Join is interesting in that it's sort of between String.Concat and String.Format. I usually overlook String.Join because, like I said, I prefer String.Format. But, look at how the Joiner method requires the same amount of IL code to be generated as String.Format. There really isn't any additional formatting required here, so String.Join is probably the second best solution.
Assume for a second that we didn't have any spacing in our strings, and instead had "The", "cake", "is", "a", "lie". Instead of passing these to String.Concat with a space such as:
string space = " "; // typing the following line gets boring very quickly: return String.Concat(string[0], space, string[1], space, string[2], space // etc...
we could use String.Join:
return String.Join(" ", strings));
Conclusion
Here is a quick summary of the findings of this little exploration:
- String.Concat-- Good for joining strings without any additional processing
- String.Join-- Good for joining strings with a specified delimeter
- String.Format-- Same amount of code for String.Join, but allows for additional formatting of strings
- String (+) operator-- Unnecessary amount of overhead when used to simply combine supplied strings
- StringBuilder-- Overly bloated method for simple concatenation.
Programming to Interfaces or Objects?
The other day, I was asked to add some functionality to code that regularly instantiates objects as interfaces, and I was wondering what exactly is the point? I understand the polymorphic aspect of interfaces, and I fully agree that interfaces should be used as parameter and return types. But, is there some sort of performance gain when implementing an object as the interface rather than the object?
To test this question, I'm going to run a simple console application to list two actors and 2 or 3 movies for each one.
Here is the code required to run the application:
class Actor
{
public string Name { get; set; }
public int Age { get; set; }
public decimal AnnualSalary { get; set; }
public List<Movie> Roles { get; set; }
public override string ToString()
{
return String.Format("{0}({1}), {2:c}/year",
Name, Age, AnnualSalary);
}
}
class Movie
{
public string Title { get; set; }
public int ReleaseYear { get; set; }
public string Director { get; set; }
public decimal Budget { get; set; }
public override string ToString()
{
return String.Format("{0}({1}), {2} [{3:c}]",
Title, ReleaseYear, Director, Budget);
}
}
class MockRepository
{
internal List<Actor> GetActors()
{
List<Actor> actors = new List<Actor>();
actors.AddRange(new List<Actor>
{
new Actor { Name = "Zooey Deschanel",
Age = Convert.ToInt32(
DateTime.Now.Subtract(
new DateTime(1980, 1, 17)
).TotalDays) / 365,
AnnualSalary = 1000000,
Roles = GetMovies("Zooey")
},
new Actor {
Name = "Tony Jaa",
Age = Convert.ToInt32(
DateTime.Now.Subtract(
new DateTime(1976, 2, 5)
).TotalDays) / 365,
AnnualSalary = 5000000,
Roles = GetMovies("Tony")
}
});
return actors;
}
internal List<Movie> GetMovies(string actor)
{
List<Movie> movies = new List<Movie>();
switch (actor)
{
case "Zooey":
movies.Add(new Movie
{
Director = "Michael Clancy",
Title = "Eulogy",
ReleaseYear = 2004,
Budget = 10000000
});
movies.Add(new Movie
{
Title = "(500) Days of Summer",
Director = "Marc Webb",
ReleaseYear = 2009,
Budget = 7500000
});
movies.Add(new Movie
{
Title = "Yes Man",
Director = "Peyton Reed",
ReleaseYear = 2008,
Budget = 50000000
});
break;
case "Tony":
movies.Add(new Movie
{
Title = "Ong Bak 2",
Director = "Panna Rittikrai",
ReleaseYear = 2008,
Budget = 50000000
});
movies.Add(new Movie
{
Title = "Ong Bak",
Director = "Prachya Pinkaew",
ReleaseYear = 2003,
Budget = 50000000
});
break;
default:
break;
}
return movies;
}
}
And, here is the console application:
static void Main(string[] args)
{
MockRepository repository = new MockRepository();
List<Actor> actors = repository.GetActors();
foreach (Actor actor in actors)
{
Console.WriteLine(actor.ToString());
foreach (Movie movie in actor.Roles)
{
Console.WriteLine("\t{0}", movie.ToString());
}
}
Console.ReadLine();
}
This produces the following output (salaries and annual budgets aren't correct):
Zooey Deschanel(30), $1,000,000.00/year
Eulogy(2004), Michael Clancy [$10,000,000.00]
(500) Days of Summer(2009), Marc Webb [$7,500,000.00]
Yes Man(2008), Peyton Reed [$50,000,000.00]
Tony Jaa(34), $5,000,000.00/year
Ong Bak 2(2008), Panna Rittikrai [$50,000,000.00]
Ong Bak(2003), Prachya Pinkaew [$50,000,000.00]
I've gone back and modified the above code to return interfaces instead of objects.
// Note: IList does not implement AddRange. So instead of:
IList<IActor> actors = new List<IActor>();
actors.AddRange(new List<IActor> { /* etc */ } );
// We'll have to do:
List<IActor> actors = new List<IActor>();
actors.AddRange(new List<IActor> { /* etc */ });
For *fun* I've outputted the Intermediate Language for each console application and run a diff between the two. The code follows, but it is easily summed up as: the code is relatively the same.
41c41
< // MVID: {D457D4D6-EFDE-4F7F-9A7D-F77E2FB92D1F}
---
> // MVID: {EF8FAF3C-306F-4E76-AE54-3A4CA179FAB7}
47c47
< // Image base: 0x00400000
---
> // Image base: 0x003E0000
61,65c61,65
< [1] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor> actors,
< [2] class InterfacesOrObjects.IActor actor,
< [3] class InterfacesOrObjects.IMovie movie,
< [4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IActor> CS$5$0000,
< [5] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IMovie> CS$5$0001,
---
> [1] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor> actors,
> [2] class InterfacesOrObjects.Actor actor,
> [3] class InterfacesOrObjects.Movie movie,
> [4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Actor> CS$5$0000,
> [5] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Movie> CS$5$0001,
71c71
< IL_0008: callvirt instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor> InterfacesOrObjects.MockRepository::GetActors()
---
> IL_0008: callvirt instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor> InterfacesOrObjects.MockRepository::GetActors()
75c75
< IL_0010: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor>::GetEnumerator()
---
> IL_0010: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor>::GetEnumerator()
82c82
< IL_001b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IActor>::get_Current()
---
> IL_001b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Actor>::get_Current()
86c86
< IL_0023: callvirt instance string InterfacesOrObjects.IActor::ToString()
---
> IL_0023: callvirt instance string [mscorlib]System.Object::ToString()
91,92c91,92
< IL_0030: callvirt instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> InterfacesOrObjects.IActor::get_Roles()
< IL_0035: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>::GetEnumerator()
---
> IL_0030: callvirt instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> InterfacesOrObjects.Actor::get_Roles()
> IL_0035: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>::GetEnumerator()
99c99
< IL_0040: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IMovie>::get_Current()
---
> IL_0040: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Movie>::get_Current()
104c104
< IL_004d: callvirt instance string InterfacesOrObjects.IMovie::ToString()
---
> IL_004d: callvirt instance string [mscorlib]System.Object::ToString()
110c110
< IL_005b: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IMovie>::MoveNext()
---
> IL_005b: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Movie>::MoveNext()
121c121
< IL_006a: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IMovie>
---
> IL_006a: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Movie>
129c129
< IL_007b: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IActor>::MoveNext()
---
> IL_007b: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Actor>::MoveNext()
140c140
< IL_008a: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.IActor>
---
> IL_008a: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class InterfacesOrObjects.Actor>
197c197
< instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>
---
> instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>
203c203
< instance void set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> 'value') cil managed
---
> instance void set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> 'value') cil managed
228c228
< .property instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>
---
> .property instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>
231,232c231,232
< .get instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> InterfacesOrObjects.IActor::get_Roles()
< .set instance void InterfacesOrObjects.IActor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>)
---
> .get instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> InterfacesOrObjects.IActor::get_Roles()
> .set instance void InterfacesOrObjects.IActor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>)
246c246
< .field private class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> '<Roles>k__BackingField'
---
> .field private class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> '<Roles>k__BackingField'
334c334
< instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>
---
> instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>
340c340
< .locals init (class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> V_0)
---
> .locals init (class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> V_0)
342c342
< IL_0001: ldfld class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> InterfacesOrObjects.Actor::'<Roles>k__BackingField'
---
> IL_0001: ldfld class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> InterfacesOrObjects.Actor::'<Roles>k__BackingField'
351c351
< instance void set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> 'value') cil managed
---
> instance void set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> 'value') cil managed
358c358
< IL_0002: stfld class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> InterfacesOrObjects.Actor::'<Roles>k__BackingField'
---
> IL_0002: stfld class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> InterfacesOrObjects.Actor::'<Roles>k__BackingField'
415c415
< .property instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>
---
> .property instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>
418,419c418,419
< .set instance void InterfacesOrObjects.Actor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>)
< .get instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> InterfacesOrObjects.Actor::get_Roles()
---
> .set instance void InterfacesOrObjects.Actor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>)
> .get instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> InterfacesOrObjects.Actor::get_Roles()
699c699
< .method assembly hidebysig instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor>
---
> .method assembly hidebysig instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor>
704,705c704,705
< .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor> actors,
< [1] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor> '<>g__initLocal0',
---
> .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor> actors,
> [1] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor> '<>g__initLocal0',
708c708
< [4] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor> CS$1$0000,
---
> [4] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor> CS$1$0000,
712c712
< IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor>::.ctor()
---
> IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor>::.ctor()
715c715
< IL_0008: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor>::.ctor()
---
> IL_0008: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor>::.ctor()
751,752c751,752
< IL_006f: call instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> InterfacesOrObjects.MockRepository::GetMovies(string)
< IL_0074: callvirt instance void InterfacesOrObjects.Actor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>)
---
> IL_006f: call instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> InterfacesOrObjects.MockRepository::GetMovies(string)
> IL_0074: callvirt instance void InterfacesOrObjects.Actor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>)
755c755
< IL_007b: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor>::Add(!0)
---
> IL_007b: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor>::Add(!0)
791,792c791,792
< IL_00e1: call instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> InterfacesOrObjects.MockRepository::GetMovies(string)
< IL_00e6: callvirt instance void InterfacesOrObjects.Actor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>)
---
> IL_00e1: call instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> InterfacesOrObjects.MockRepository::GetMovies(string)
> IL_00e6: callvirt instance void InterfacesOrObjects.Actor::set_Roles(class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>)
795c795
< IL_00ed: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor>::Add(!0)
---
> IL_00ed: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor>::Add(!0)
798c798
< IL_00f4: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IActor>::AddRange(class [mscorlib]System.Collections.Generic.IEnumerable`1<!0>)
---
> IL_00f4: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Actor>::AddRange(class [mscorlib]System.Collections.Generic.IEnumerable`1<!0>)
808c808
< .method assembly hidebysig instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>
---
> .method assembly hidebysig instance class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>
813c813
< .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> movies,
---
> .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> movies,
819c819
< [6] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie> CS$1$0000,
---
> [6] class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie> CS$1$0000,
822c822
< IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>::.ctor()
---
> IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>::.ctor()
864c864
< IL_0072: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>::Add(!0)
---
> IL_0072: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>::Add(!0)
887c887
< IL_00b5: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>::Add(!0)
---
> IL_00b5: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>::Add(!0)
910c910
< IL_00f8: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>::Add(!0)
---
> IL_00f8: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>::Add(!0)
935c935
< IL_0146: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>::Add(!0)
---
> IL_0146: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>::Add(!0)
958c958
< IL_018f: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.IMovie>::Add(!0)
---
> IL_018f: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class InterfacesOrObjects.Movie>::Add(!0)
988c988
< // WARNING: Created Win32 resource file C:\Users\Jim\Desktop\Interfaces or Objects\AsInterfaces.res
---
> // WARNING: Created Win32 resource file C:\Users\Jim\Desktop\Interfaces or Objects\AsObjects.res
This leads me to the question, why would anyone program implementations as interfaces? In Visual Studio, when you right click and choose "Go to declaration" on a method implemented as an interface instead of an object (for instance, if actor.Roles used a getter to perform some action on movies), you will be directed to the interface, not to the actual method of the object. As noted above, IList doesn't implement AddRange, so we'd have to explicitly cast the interface implementation in order to use the desired method.
Is it possible that interfaces can be overdone?
I don't think there is any huge benefit to using interfaces unless your application is going to be "interfaced" with, or if there is some amount of inheritance or polymorphism in the application. In the example posted above, the interfaces are completely unnecessary.
Fluent NHibernate error: Unable to cast object of type ‘Oracle.DataAccess.Client.OracleConnection’ to type ‘System.Data.Common.DbConnection’.
After developing an application in Fluent NHibernate, I've received the following Error and Stack Trace:
Unable to cast object of type 'Oracle.DataAccess.Client.OracleConnection' to type 'System.Data.Common.DbConnection'. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.InvalidCastException: Unable to cast object of type 'Oracle.DataAccess.Client.OracleConnection' to type 'System.Data.Common.DbConnection'. Source Error: An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. Stack Trace: [InvalidCastException: Unable to cast object of type 'Oracle.DataAccess.Client.OracleConnection' to type 'System.Data.Common.DbConnection'.] NHibernate.Tool.hbm2ddl.SuppliedConnectionProviderConnectionHelper.Prepare() +43 NHibernate.Tool.hbm2ddl.SchemaMetadataUpdater.GetReservedWords(Dialect dialect, IConnectionHelper connectionHelper) +65 NHibernate.Tool.hbm2ddl.SchemaMetadataUpdater.Update(ISessionFactory sessionFactory) +80 NHibernate.Impl.SessionFactoryImpl..ctor(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners) +598 NHibernate.Cfg.Configuration.BuildSessionFactory() +87 FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory() +49 [FluentConfigurationException: An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail. ] FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory() +69 ETeacherWeb.DataAccess.DataSession.get_SessionFactory() +641 ETeacherWeb.HttpModules.NHibernateSessionModule.<Init>b__0(Object , EventArgs ) +8 System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +68 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75
The server is running Oracle 9.2 client, without ODP.NET. In order to fix this, I copied version 9.2 libraries to the server.
The libraries required were:
Oracle.DataAccess.dll (application/bin folder)
OraOps9.dll (oracle/9.2/bin)
The above directories will of course be different for you, but they should help. Also, the Oracle bin folder must be accessible via the %PATH% variable.
If possible, the much easier fix is to install a full Oracle client, including ODP.NET.
IHttpModule Gotchas @dominicpettifer.co.uk
I came across this blog post about the Init() method of an HttpModule being called more than once. The solution is simple: create a boolean flag to indicate whether or not the app is started and an empty object to lock. Then, in the Init() method, check the flag, lock the object, then check the flag again before performing any application code.
For example:
private static bool HasAppStarted = false;
private readonly static object _syncObject = new object();
public void Init(HttpApplication context)
{
if (!HasAppStarted)
{
lock (_syncObject)
{
if (!HasAppStarted)
{
// Run application StartUp code here
HasAppStarted = true;
}
}
}
}
The full blog post can be viewed here.
I was directed to the above post via The Morning Brew.
ArgumentOutOfRangeException : Index was out of range. Fluent NHibernate commit
I came across the error
ArgumentOutOfRangeException : Index was out of range.
while trying to perform a commit on a single entity with 4 nested entities and 3 properties.
I tracked the problem down to one two entities: Country and "Post" which I will refer to as Location.
The objects looked like:
public class Location
{
...
// public virtual string ContactCountry { get; set; }
...
public virtual Country Country { get; set; }
}
public class Country
{
...
public virtual IList<Location> Locations { get; set; }
}
The problem came from the Fluent NHibernate mapping referring to a property on the COUNTRY_CODE field and specifying a HasMany relationship to Country on the same column.
This is an oddity, for sure. But, the better (object-oriented and proper) way to do it would be to change ContactCountry to a getter and return the Country.Code property (if ContactCountry is used elsewhere). Or, call Location.Country.Code only.
Removing the property and mapping for ContactCountry fixed the problem.
log4net configuration in .NET 3.5
Step 1 Add Reference
Right click on the "References" folder and choose add reference. Browse to the location of log4net and add it to the project.
Step 2 Add Config Section Reference
In web.config, add a reference to the log4net config section handler. This will look like:
<?xml version="1.0"?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/> <!-- other sections/sectionGroups --> </configSections> </configuration>
Step 3 Add the log4net configuration Section
All that is left is to add the actual configuration. There are an unlimited number of ways this can be done, with numerous different logs and log types. An example configuration follows. This configuration creates a log for NHibernate with a max size of 8MB, a rolling file (max size 3MB), and a a console log. Anything logged to the logger named NHibernate.SQL will record only if they are ERROR level or higher. Anything logged without specifying a logger name is logged to root, and only goes to the console and rolling file.
<log4net>
<appender name="NHibernateFileLog" type="log4net.Appender.RollingFileAppender,log4net">
<file value="logs/nhibernate.txt"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="0"/>
<maximumFileSize value="8MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout,log4net">
<conversionPattern value="%d [%t] %-5p %c - %m%n"/>
</layout>
</appender>
<appender name="console" type="log4net.Appender.ConsoleAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d{ABSOLUTE} %-5p %c{1}:%L - %m%n"/>
</layout>
</appender>
<!-- log4net uses 5 levels, namely DEBUG, INFO, WARN, ERROR and FATAL. -->
<appender name="rollingFile" type="log4net.Appender.RollingFileAppender,log4net">
<param name="File" value="logs/RollingLog.txt"/>
<param name="AppendToFile" value="false"/>
<param name="RollingStyle" value="Date"/>
<param name="DatePattern" value="yyyy.MM.dd"/>
<param name="StaticLogFileName" value="true"/>
<maximumFileSize value="3MB"/>
<maxSizeRollBackups value="0"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n"/>
</layout>
</appender>
<logger name="NHibernate.SQL" additivity="false">
<level value="ERROR"/>
<appender-ref ref="NHibernateFileLog"/>
<appender-ref ref="console"/>
<appender-ref ref="rollingFile"/>
</logger>
<root>
<level value="ERROR"/>
<appender-ref ref="console"/>
<appender-ref ref="rollingFile"/>
<!--Uncomment the following appender for verbose output (degrades performance)-->
<!--<appender-ref ref="rollingFile"/>-->
</root>
</log4net>
Step 4
Now, all that's left is to call log4net's configure method before any other (read: **error prone**) code is called.
For example, in an ASP.NET Web application, you could add this to Global.asax:
public class GlobalAsax : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
log4net.Config.XmlConfigurator.Configure();
}
}
Other logging options include database, mail, net, access.
For more information and examples on log4net, visit http://logging.apache.org/log4net/release/config-examples.html
An example Logger implementation in C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using log4net;
using log4net.Config;
namespace ETeacherWeb.Common
{
/// <summary>
/// Logger class for log4net programmatic logging
/// </summary>
public static class Logger
{
/// <summary>
/// Severity/Level of the log entry
/// </summary>
public enum LogLevel
{
DEBUG = 1,
ERROR,
FATAL,
INFO,
WARN
}
#region Members
private static readonly ILog logger = LogManager.GetLogger(typeof(Logger));
#endregion
#region Constructors
static Logger()
{
XmlConfigurator.Configure();
}
#endregion
#region Methods
/// <summary>
/// Write a string to the log with a specified level of severity
/// </summary>
/// <param name="logLevel">The severity of the log entry</param>
/// <param name="log">The log entry</param>
public static void WriteLog(LogLevel logLevel, String log)
{
if (logLevel.Equals(LogLevel.DEBUG))
{
logger.Debug(log);
}
else if (logLevel.Equals(LogLevel.ERROR))
{
logger.Error(log);
}
else if (logLevel.Equals(LogLevel.FATAL))
{
logger.Fatal(log);
}
else if (logLevel.Equals(LogLevel.INFO))
{
logger.Info(log);
}
else if (logLevel.Equals(LogLevel.WARN))
{
logger.Warn(log);
}
}
#endregion
}
}
To use the above code, you would call
Logger.WriteLog(LogLevel.DEBUG, "The expected logic failed validation");
