Tuesday, August 28, 2012

Deep copy of C# object properties

Sometimes you have two different class types with the same properties and you want to copy all the property values from one to the other.

One way is to simply hand code one assignment statement for each field they have in common.

Another option is to use an open source mapping library like AutoMapper

Still other times you might find yourself in the middle ground of not wanting to hand code all the assignments and also not wanting to deal with a 3rd party library.

Here is an example of how you can roll your own automapper in a few lines of code.  This snippet copies like-named properties from a source to a target.  It recurses on any properies that don't have a type starting with "System." and it ignores source properties with value of null.

static void CopyProperties(object sourceObject, object targetObject, bool deepCopy = true)
{
if (sourceObject != null && targetObject != null)
{
(from sourceProperty in sourceObject.GetType().GetProperties().AsEnumerable()
from targetProperty in targetObject.GetType().GetProperties().AsEnumerable()
where sourceProperty.Name.ToUpper() == targetProperty.Name.ToUpper()
let sourceValue = sourceProperty.GetValue(sourceObject, null)
where sourceValue != null
select CopyProperty(targetProperty, targetObject, sourceValue, deepCopy))
.ToList()
.ForEach(c => c());
}
}
static Action CopyProperty(PropertyInfo propertyInfo, object targetObject, object sourceValue, bool deepCopy)
{
if (!deepCopy || sourceValue.GetType().FullName.StartsWith("System."))
return () => propertyInfo.SetValue(targetObject, sourceValue, null);
else
return () => CopyProperties(sourceValue, propertyInfo.GetValue(targetObject, null));
}

Friday, August 24, 2012

RadioButton panel based on an enum type in a C# WinForms application


public class RadioButtonPanel<T> : FlowLayoutPanel
{
public RadioButtonPanel()
{
var graphics = this.CreateGraphics();
var zeroPadding = new Padding(0);
var initialPadding = new Padding(10, 0, 0, 0);
int radioButtonCircleWidth = 30;
graphics.MeasureString("a", RadioButton.DefaultFont);
bool first = true;
foreach (object value in Enum.GetValues(typeof(T)))
{
string name = Enum.GetName(typeof(T), value);
var button = new RadioButton
{
Text = name,
Checked = first,
Padding = zeroPadding,
Margin = first ? initialPadding : zeroPadding,
Width = (int)graphics.MeasureString(name, RadioButton.DefaultFont).Width + radioButtonCircleWidth
};
first = false;
button.CheckedChanged += (s, e) =>
{
if (button.Checked && this.Selected != null)
{
T current = (T)Enum.Parse(typeof(T), name);
this.Current = current;
Selected(current);
}
};
this.Controls.Add(button);
}
this.Current = (T)(object)0;
this.Padding = zeroPadding;
this.Margin = zeroPadding;
}
public event SelectedEvent Selected;
public delegate void SelectedEvent(T t);
public T Current { get; set; }
}

Friday, August 17, 2012

How do mixins work in C#?

Extension methods which target a common marker interface can be viewed as partial implementations of that interface.

If you place such extension methods in different namespaces then it is possible to utilize a technique of "Mixing In" the implementations used by a class.

This is demonstrated in code below.  The one part of this that I took a minute to get my head around was that ILogger loses the semantics of an interface.  The methods available to it are those extension methods which are visible to the compiler as dictated by the using statements.

Some links I learned from before writing this:
namespace MixinDemo
{
public interface ILogger
{
}
}
view raw ILogger.cs hosted with ❤ by GitHub
using System;
namespace MixinDemo.Logger1.Log
{
public static class Mixin
{
public static void Log(this ILogger logger, string message)
{
Console.WriteLine("Logger1 Log: " + message);
}
}
}
view raw Log.cs hosted with ❤ by GitHub
using System;
namespace MixinDemo.Logger2.LogError
{
public static class Mixin
{
public static void LogError(this ILogger logger, string message)
{
Console.WriteLine("Logger2 LogError: " + message);
}
}
}
view raw LogError.cs hosted with ❤ by GitHub
using System;
namespace MixinDemo
{
// Mix in the ILogger functionality required by the implementation of the Program class
using Logger1.Log;
using Logger2.LogError;
class Program
{
ILogger logger = null; // OK to leave uninitialized since only extension methods will be called
static void Main(string[] args)
{
new Program().Execute();
}
private void Execute()
{
try
{
logger.Log("Hello World");
(null as object).GetType(); // error
}
catch (Exception ex)
{
logger.LogError(ex.Message);
}
}
}
}
view raw Program.cs hosted with ❤ by GitHub