Background:
I have an attribute that indicates that a property of field in an object IsMagic
. I also have a Magician
class that runs over any object and MakesMagic
by extracting each field and property that IsMagic
and wraps it in a Magic
wrapper.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace MagicTest
{
/// <summary>
/// An attribute that allows us to decorate a class with information that identifies which member is magic.
/// </summary>
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field, AllowMultiple = false)]
class IsMagic : Attribute { }
public class Magic
{
// Internal data storage
readonly public dynamic value;
#region My ever-growing list of constructors
public Magic(int input) { value = input; }
public Magic(string input) { value = input; }
public Magic(IEnumerable<bool> input) { value = input; }
// ...
#endregion
public bool CanMakeMagicFromType(Type targetType)
{
if (targetType == null) return false;
ConstructorInfo publicConstructor = typeof(Magic).GetConstructor(new[] { targetType });
if (publicConstructor != null) return true; // We can make Magic from this input type!!!
return false;
}
public override string ToString()
{
return value.ToString();
}
}
public static class Magician
{
/// <summary>
/// A method that returns the members of anObject that have been marked with an IsMagic attribute.
/// Each member will be wrapped in Magic.
/// </summary>
/// <param name="anObject"></param>
/// <returns></returns>
public static List<Magic> MakeMagic(object anObject)
{
Type type = anObject?.GetType() ?? null;
if (type == null) return null; // Sanity check
List<Magic> returnList = new List<Magic>();
// Any field or property of the class that IsMagic gets added to the returnList in a Magic wrapper
MemberInfo[] objectMembers = type.GetMembers();
foreach (MemberInfo mi in objectMembers)
{
bool isMagic = (mi.GetCustomAttributes<IsMagic>().Count() > 0);
if (isMagic)
{
dynamic memberValue = null;
if (mi.MemberType == MemberTypes.Property) memberValue = ((PropertyInfo)mi).GetValue(anObject);
else if (mi.MemberType == MemberTypes.Field) memberValue = ((FieldInfo)mi).GetValue(anObject);
if (memberValue == null) continue;
returnList.Add(new Magic(memberValue)); // This could fail at run-time!!!
}
}
return returnList;
}
}
}
The Magician can MakeMagic
on anObject
with at least one field or property that IsMagic
to produce a generic List
of Magic
, like so:
using System;
using System.Collections.Generic;
namespace MagicTest
{
class Program
{
class Mundane
{
[IsMagic] public string foo;
[IsMagic] public int feep;
public float zorp; // If this [IsMagic], we'll have a run-time error
}
static void Main(string[] args)
{
Mundane anObject = new Mundane
{
foo = "this is foo",
feep = -10,
zorp = 1.3f
};
Console.WriteLine("Magic:");
List<Magic> myMagics = Magician.MakeMagic(anObject);
foreach (Magic aMagic in myMagics) Console.WriteLine(" {0}",aMagic.ToString());
Console.WriteLine("More Magic: {0}", new Magic("this works!"));
//Console.WriteLine("More Magic: {0}", new Magic(Mundane)); // build-time error!
Console.WriteLine("\nPress Enter to continue");
Console.ReadLine();
}
}
}
Notice that Magic
wrappers can only go around properties or fields of certain types. This means that only property or field that contains data of specific types should be marked as IsMagic
. To make matters more complicated, I expect the list of specific types to change as business needs evolve (since programming Magic is in such high demand).
The good news is that the Magic
has some build time safety. If I try to add code like new Magic(true)
Visual Studio will tell me it's wrong, since there is no constructor for Magic
that takes a bool
. There is also some run-time checking, since the Magic.CanMakeMagicFromType
method can be used to catch problems with dynamic variables.
Problem Description:
The bad news is that there's no build-time checking on the IsMagic
attribute. I can happily say a Dictionary<string,bool>
field in some class IsMagic
, and I won't be told that it's a problem until run-time. Even worse, the users of my magical code will be creating their own mundane classes and decorating their properties and fields with the IsMagic
attribute. I'd like to help them see problems before they become problems.
Proposed Solution:
Ideally, I could put some kind of AttributeUsage flag on my IsMagic
attribute to tell Visual Studio to use the Magic.CanMakeMagicFromType()
method to check the property or field type that the IsMagic
attribute is being attached to. Unfortunately, there doesn't seem to be such an attribute.
However, it seems like it should be possible to use Roslyn to present an error when IsMagic
is placed on a field or property that has a Type
that can't be wrapped in Magic
.
Where I need help:
I am having trouble designing the Roslyn analyser. The heart of the problem is that Magic.CanMakeMagicFromType
takes in System.Type
, but Roslyn uses ITypeSymbol
to represent object types.
The ideal analyzer would:
- Not require me to keep a list of allowed types that can be wrapped in
Magic
. After all,Magic
has a list of constructors that serve this purpose. - Allow natural casting of types. For instance, if
Magic
has a constructor that takes inIEnumerable<bool>
, then Roslyn should allowIsMagic
to be attached to a property with typeList<bool>
orbool[]
. This casting of Magic is critical to the Magician's functionality.
I'd appreciate any direction on how to code a Roslyn analyzer that is "aware" of the constructors in Magic
.