Skip to content

Instantly share code, notes, and snippets.

@cerebrate
Created September 5, 2013 13:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cerebrate/6449820 to your computer and use it in GitHub Desktop.
Save cerebrate/6449820 to your computer and use it in GitHub Desktop.
PostSharp: Doing Singletons Right
namespace ArkaneSystems.Arkane.Aspects
{
/// <summary>
/// A constraint which verifies that the class to which it is applied obeys the Singleton pattern
/// we use.
/// </summary>
/// <remarks>
/// The singleton pattern is as follows:
/// private static readonly Lazy{$CLASS$} lazy = new Lazy{$CLASS$} (() => new $CLASS$());
/// public static $CLASS$ Instance { get { return lazy.Value; } }
/// private $CLASS$ ()
/// {}
/// </remarks>
[MulticastAttributeUsage (MulticastTargets.Class, Inheritance = MulticastInheritance.None)]
public sealed class SingletonConstraint : ScalarConstraint
{
/// <summary>
/// Validates the element of code to which the constraint is applied.
/// </summary>
/// <param name="target">
/// Element of code to which the constraint is applied (<see cref="T:System.Reflection.Assembly" />,
/// <see cref="T:System.Type" />,
/// <see cref="T:System.Reflection.MethodInfo" />, <see cref="T:System.Reflection.ConstructorInfo" />,
/// <see cref="T:System.Reflection.PropertyInfo" />,
/// <see cref="T:System.Reflection.EventInfo" />, <see cref="T:System.Reflection.FieldInfo" />,
/// <see cref="T:System.Reflection.ParameterInfo" />).
/// </param>
public override void ValidateCode (object target)
{
var targetType = (Type) target;
// Check for private static readonly field, named lazy, typed Lazy<T>.
// Make required generic type.
Type generic = (typeof (Lazy<>)).MakeGenericType (targetType);
var field = targetType.GetField ("Lazy", BindingFlags.NonPublic | BindingFlags.Static);
if (field == null || field.IsPrivate != true || field.FieldType != generic || field.IsInitOnly != true)
{
Message.Write (targetType,
SeverityType.Error,
"2001",
"The {0} type does not have 'private static readonly Lazy<{0}> lazy = new Lazy<{0}> (() => new {0} ());'.",
targetType.Name);
}
// Check for public static property, read-only, named Instance, returning T.
var property = targetType.GetProperty ("Instance",
BindingFlags.Public | BindingFlags.Static,
null,
targetType,
new Type[0],
null);
if (property == null || property.CanRead != true || property.CanWrite)
{
Message.Write (targetType,
SeverityType.Error,
"2002",
"The {0} type does not have 'public static {0} Instance {{ get {{ return lazy.Value; }} }}'.",
targetType.Name);
}
// Check for private static parameterless constructor and absence of other constructors.
var constructors =
targetType.GetConstructors (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
bool ppc = false;
bool other = false;
foreach (ConstructorInfo c in constructors)
{
if (c.IsPrivate && (c.GetParameters ().Length == 0))
ppc = true;
else
other = true;
}
if (!ppc || other)
{
Message.Write (targetType,
SeverityType.Error,
"2003",
"The {0} type does not have a single, parameterless private constructor.",
targetType.Name);
}
}
}
}
@cerebrate
Copy link
Author

(Checks to make sure all [SingletonConstraint] flagged singletons follow the same appropriate pattern.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment