Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Gotcha When C# Structures Implement Interfaces

0.00/5 (No votes)
30 Aug 2015 2  
Gotcha when passing in structures to a method that expects an Interface as a parameter

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.

// Put this code in a Linq Pad session to run it

void Main()
{
    // Declare a struct that implements the interface IWorkItem
    var wt = new WorkItem("asdf", 5);
    
    // Try to change the structure without being cast to an interface type    
    ChangeWork(wt, "pqrs", -4);
    
    // Check to see if the values in the struct changed
    Console.WriteLine(string.Format
    ("wt - WorkType: {0} WorkHours: {1}", wt.WorkType, wt.WorkHours));
    // Humm... not changed
    
    // Ok cast it as an Interface type i.e reference type, i.e. box the value
    var iwt = wt as IWorkItem;
    
    // Structure cast to interface type, 
    // now the value is copied and boxed, so try and change it    
    ChangeWork(iwt, "pqrs", -4);
    
    // Result of the operation on the boxed value
    Console.WriteLine(string.Format
    ("iwt - WorkType: {0} WorkHours: {1}", iwt.WorkType, iwt.WorkHours));    
        
    // Original struct, wt, remains the same since only its boxed copy was changed
    Console.WriteLine(string.Format
    ("wt - WorkType: {0} WorkHours: {1}", wt.WorkType, wt.WorkHours));
    
    // Create an instance of a class that implements IWorkItem
    var wic = new WorkItemClass("hklm", 6);
    
    // Create a collection of IWorkItem types
    var lstStruct = new List<IWorkItem> { wt, wic };
    
    // Change the collection of IWorkItem types
    ChangeWorkList(lstStruct, "qwerty", -1);
    
    // Items in the collection have changed
    foreach(var i in lstStruct)
    {
       Console.WriteLine(string.Format
       ("WorkType: {0} WorkHours: {1}", i.WorkType, i.WorkHours));
    }
    
    // The value of the reference type added to the collection has changed
    // But the value of the struct outside the collection remains the same, 
    // again, it was copied and boxed
    // and only its boxed copy was changed
    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));
}

// Define other methods and classes here

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 structs 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

  • Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here