void Main() { typeof(NonConforming).MustConformToOneOf(new PropertyConventionSpecification[] { new PropertiesMustHavePrivateSettersConventionSpecification(), new PropertiesMustHavePublicSettersConventionSpecification() }); } public class NonConforming { public string Name {get;set;} public string PrivateName {get;private set;} public string ProtectedName {get;protected set;} } public static class PropertyConventionConformist { public static PropertyConventionResult[] MustConformToOneOf(this Type type, PropertyConventionSpecification[] conventionSpecifications) { var results = conventionSpecifications.Select(x => x.IsSatisfiedBy(type)).ToArray(); if (results.All(r => r.IsSatisfied)) { return results; } var groupedFailures = results.SelectMany(r => r.Failures).GroupBy(k => k.Subject); if (groupedFailures.Any(g => g.Count() == conventionSpecifications.Count())) { "Fail Whale".Dump(); } else { "Success".Dump(); } return results; } } public class PropertiesMustHavePrivateSettersConventionSpecification : PropertyConventionSpecification { protected override string FailureMessage => "All properties must have private setters"; protected override PropertyInfo[] GetNonConformingProperties(Type type) { return type.GetDeclaredProperties() .Where(subject => subject.CanWrite == false || subject.GetSetMethod(true).IsPrivate == false) .ToArray(); } } public class PropertiesMustHavePublicSettersConventionSpecification : PropertyConventionSpecification { protected override string FailureMessage => "All properties must have public setters"; protected override PropertyInfo[] GetNonConformingProperties(Type type) { return type.GetDeclaredProperties() .Where(subject => subject.CanWrite == false || subject.GetSetMethod(true).IsPublic == false) .ToArray(); } } public abstract class PropertyConventionSpecification : ConventionSpecification { protected abstract PropertyInfo[] GetNonConformingProperties(Type type); public override PropertyConventionResult IsSatisfiedBy(Type type) { var failures = GetNonConformingProperties(type); if (failures.Any()) { var failureMessage = BuildFailureMessage(failures.Aggregate(string.Empty, (s, info) => s + "\t- " + info.Name + Environment.NewLine)); return PropertyConventionResult.NotSatisfied(type, failures.Select(f => new Failure(f, "failed")).ToArray()); } return PropertyConventionResult.Satisfied(type); } } public abstract class ConventionSpecification : IConventionSpecification where TResult : IHaveSubject { protected abstract string FailureMessage { get; } protected string BuildFailureMessage(string details) { return FailureMessage + Environment.NewLine + details; } public abstract TResult IsSatisfiedBy(TSubject type); } public interface IConventionSpecification where TResult : IHaveSubject { TResult IsSatisfiedBy(TSubject type); } public class PropertyConventionResult : ConventionResult { public PropertyConventionResult(Type subject) : base(subject) { } public static PropertyConventionResult Satisfied(Type subject) { return new PropertyConventionResult(subject) { IsSatisfied = true }; } public static PropertyConventionResult NotSatisfied(Type subject, Failure[] failures) { return new PropertyConventionResult(subject) { Failures = failures }; } } public class GenericConventionResult : ConventionResult { public GenericConventionResult(string subject) : base(subject) { } } // Define other methods and classes here public class ConventionResult : IHaveSubject { public ConventionResult(TSubject subject) { Subject = subject; Failures = new Failure[0]; } public TSubject Subject { get; } public bool IsSatisfied { get; protected set; } public IReadOnlyCollection> Failures { get; protected set; } } public interface IHaveSubject { T Subject { get; } } public class Failure { public Failure(T subject, string message) { Subject = subject; Message = message; } public T Subject {get;} public string Message {get;} } public static class TypeExtensions { internal static PropertyInfo[] GetDeclaredProperties(this Type type) { return type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); } internal static bool IsGenericImplementation(this Type type) { return (type.BaseType != null && type.BaseType.IsGenericType) || type.GetInterfaces().Any(i => i.IsGenericType); } public static IEnumerable WhereTypes(this IEnumerable assemblies, Func predicate) { var types = assemblies.SelectMany(x => x.GetExportedTypes()).Where(predicate).ToArray(); return types; } }