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.