A discussion has come up at work:
We've got a class that has an IList. Fact is an abstract base class, and there are several concrete subclasses (PopulationFact, GdpFact, etc).
Originally we'd query for a given fact in this fashion, i.e. by type:
.Facts.FirstOrDefault(x => x.Year == 2011 && x is GdpFact)
Now, however, the question's been raised whether we should introduce a FactType enum instead to do
.Facts.FirstOrDefault(x => x.Year == 2011 && x.FactType == FactType.Gdp)
The suggestion has been raised because it is supposedly faster. I'll admit that I've not written any tests to try and discern the difference in performance, but I have two questions:
1) Is 'querying on type' like this inherently bad?
2) Isn't adding a FactType enum just superfluous anyway, given that facts are strongly typed?
UPDATE To clarify, this is LINQ to objects and GdpFact:Fact.
UPDATE 2 We've measured using current typical data (4 facts) and the results are in:
lookup on enum: 0.29660000000000003 milliseconds lookup on type: 0.24530000000000002 milliseconds
So type lookup is faster in this context! I will choose my accepted answer carefully.
It is slightly slower LINQ syntax is typically less efficient than a foreach loop. It's good to be aware of any performance tradeoff that might occur when you use LINQ to improve the readability of your code. And if you'd like to measure the performance difference, you can use a tool like BenchmarkDotNet to do so.
Stored procedures are faster as compared to LINQ query since they have a predictable execution plan and can take the full advantage of SQL features. Hence, when a stored procedure is being executed next time, the database used the cached execution plan to execute that stored procedure.
No, LINQ iterators are not and will never be faster than foreach . Also, List.
I've done a test, my results for 1000000 iterations are approximately
ByCast 166ms
ByType 84ms
ByEnum 98ms
So the enum
is in fact superfluous and slower but, not by much. This should not be too suprising, the type system is fundamental to the .Net Framework.
Test code transcribed below, apologies for errata
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
class Program
{
private enum TypeOfFact
{
Gdp,
Other
}
private abstract class Fact
{
public virtual int Year { get; set; }
public abstract TypeOfFact FactType { get; }
}
private class GdpFact : Fact
{
public override TypeOfFact FactType
{
get { return TypeOfFact.Gdp; }
}
}
private class OtherFact : Fact
{
public override TypeOfFact FactType
{
get { return TypeOfFact.Other; }
}
}
static void Main()
{
Ilist<Fact> facts = new List<Fact>
{
new GdpFact { Year = 2010 },
new OtherFact { Year = 2010 },
new GdpFact { Year = 2009 },
new OtherFact { Year = 2009 },
new GdpFact { Year = 2011 },
new OtherFact { Year = 2011 },
};
const int interations = 1000000;
var funcs = new List<Func<IList<Fact>, Fact>>
{
ByList,
ByType,
ByEnum
};
// Warmup
foreach (var func in funcs)
{
Measure(5, func, facts);
}
// Results
foreach (var result in funcs.Select(f => new
{
Description = f.Method.Name,
Ms = Measure(iterations, f, facts)
}))
{
Console.WriteLine(
"{0} time = {1}ms",
result.Description,
result.Ms);
}
}
private static long Measure(
int iterations,
Func<IList<Fact>, Fact> func,
IList<Fact> facts)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < iterations; i++)
{
func.Invoke(facts);
}
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
private static Fact ByType(IList<Fact> facts)
{
return facts.FirstOrDefault(f =>
f.Year == 2011 && f is GdpFact);
}
private static Fact ByEnum(IList<Fact> facts)
{
return facts.FirstOrDefault(f =>
f.Year == 2011 && f.FactType == TypeOfFact.Gdp);
}
private static Fact ByCast(IList<Fact> facts)
{
return facts.OfType<GdpFact>()
.FirstOrDefault(f => f.Year == 2011);
}
}
This question seems relevant.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With