Introduction
This tip highlights something to be aware of when using structures that implement Interfaces in C#.
Background
Often in C#, it is useful to use structures in place of classes. Since being value types and being allocated on the stack instead of the managed heap, they are faster to create, faster to access their data (since they do not follow a pointer to the managed heap unlike reference types) and they are faster to get cleaned up. Cleaning them up on the stack only involves reallocation of a memory address on the stack at the end of the scope of their use as opposed to a reference type which must be cleaned up by the garbage collection process.
They can be used in places where using inheritance is not necessary so they can be used as DTOs for example.
Structures That Implement Interfaces
Things get interesting when using structures that implement interfaces and then passing those structures as parameters into methods that accept Interface parameters. Take a look at the code below, rather copy it and run it in a console application or a Linq Pad session.
void Main()
{
var wt = new WorkItem("asdf", 5);
ChangeWork(wt, "pqrs", -4);
Console.WriteLine(string.Format
("wt - WorkType: {0} WorkHours: {1}", wt.WorkType, wt.WorkHours));
var iwt = wt as IWorkItem;
ChangeWork(iwt, "pqrs", -4);
Console.WriteLine(string.Format
("iwt - WorkType: {0} WorkHours: {1}", iwt.WorkType, iwt.WorkHours));
Console.WriteLine(string.Format
("wt - WorkType: {0} WorkHours: {1}", wt.WorkType, wt.WorkHours));
var wic = new WorkItemClass("hklm", 6);
var lstStruct = new List<IWorkItem> { wt, wic };
ChangeWorkList(lstStruct, "qwerty", -1);
foreach(var i in lstStruct)
{
Console.WriteLine(string.Format
("WorkType: {0} WorkHours: {1}", i.WorkType, i.WorkHours));
}
Console.WriteLine(string.Format
("wt - WorkType: {0} WorkHours: {1}", wt.WorkType, wt.WorkHours));
Console.WriteLine(string.Format
("wic - WorkType: {0} WorkHours: {1}", wic.WorkType, wic.WorkHours));
}
public void ChangeWork(IWorkItem wrk, string workType, int workHours)
{
wrk.WorkType = workType;
wrk.WorkHours = workHours;
}
public void ChangeWorkList
(IEnumerable<IWorkItem> workList, string workType, int workHours)
{
foreach(var i in workList)
{
i.WorkType = workType;
i.WorkHours = workHours;
}
}
public interface IWorkItem
{
string WorkType {get; set;}
int WorkHours {get; set;}
}
public class WorkItemClass : IWorkItem
{
public int WorkHours {get; set;}
public string WorkType {get; set;}
public WorkItemClass(string workType, int hours)
{
WorkHours = hours;
WorkType = workType;
}
}
public struct WorkItem : IWorkItem
{
public int WorkHours {get; set;}
public string WorkType {get; set;}
public WorkItem(string workType, int hours):this()
{
WorkHours = hours;
WorkType = workType;
}
}
What is happening in the above code is basically the boxing of a value type which is a copy of the struct
being made and wrapped in an object instance a reference type at the point of entry into the method.
This is what happens implicitly when the struct
is being passed into the 'ChangeWork
' method the first time. The implictly created reference type is changed within the method and then goes out of scope once the method ends. The struct 'wt
' meanwhile, outside the method remains unchanged.
Implicit boxing happens again when the struct
'wt
' is added to a list of IWorkItems
along with a reference type and changed. Again, the struct
outside the method call remains unchanged.
This is something to be aware of when passing in struct
s that implement an interface/s to methods that expect parameters of that interface.
The struct
can be changed by passing it into a method by reference but the signature of the method will have to be one that accepts the structure type rather than the interface type.
When using methods that alter the interface type, when using structures that implement said interface/s, it is important to continue processing with the interface type, such as the collection 'lstStruct
' in the example, in order to harvest the changes to items in the collection as expected, instead of reverting to the element/s that formed its parts.
History