【发布时间】:2018-10-23 11:38:11
【问题描述】:
背景:
我有一个属性表明对象IsMagic 中的字段属性。我还有一个 Magician 类,它通过提取 IsMagic 的每个字段和属性并将其包装在 Magic 包装器中来运行在任何对象和 MakesMagic 上。
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;
}
}
}
魔术师可以在anObject 上MakeMagic 使用至少一个IsMagic 的字段或属性来生成List 的通用List Magic,如下所示:
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();
}
}
}
注意Magic 包装器只能绕过某些类型的属性或字段。这意味着只有包含特定类型数据的属性或字段才应标记为IsMagic。更复杂的是,我预计特定类型的列表会随着业务需求的发展而变化(因为对 Magic 编程的需求如此之高)。
好消息是Magic 具有一定的构建时间安全性。如果我尝试添加像new Magic(true) 这样的代码,Visual Studio 会告诉我这是错误的,因为没有Magic 的构造函数接受bool。还有一些运行时检查,因为Magic.CanMakeMagicFromType 方法可用于捕捉动态变量的问题。
问题描述:
坏消息是IsMagic 属性没有构建时检查。我可以很高兴地在某个类IsMagic 中说出Dictionary<string,bool> 字段,并且直到运行时我才会被告知这是一个问题。更糟糕的是,我的神奇代码的用户将创建他们自己的普通类并使用IsMagic 属性装饰他们的属性和字段。我想帮助他们在问题变成问题之前发现问题。
建议的解决方案:
理想情况下,我可以在 IsMagic 属性上放置某种 AttributeUsage 标志,以告诉 Visual Studio 使用 Magic.CanMakeMagicFromType() 方法检查 IsMagic 属性附加到的属性或字段类型。可惜好像没有这个属性。
但是,当 IsMagic 被放置在具有无法被 Magic 包装的 Type 的字段或属性上时,似乎应该可以使用 Roslyn 来显示错误。
我需要帮助的地方:
我在设计 Roslyn 分析仪时遇到问题。问题的核心是Magic.CanMakeMagicFromType 接受了System.Type,但Roslyn 使用ITypeSymbol 来表示对象类型。
理想的分析仪应该:
- 不需要我保留可以包含在
Magic中的允许类型列表。毕竟,Magic有一个用于此目的的构造函数列表。 - 允许自然转换类型。例如,如果
Magic有一个接受IEnumerable<bool>的构造函数,那么Roslyn 应该允许IsMagic附加到类型为List<bool>或bool[]的属性上。这种魔法施法对魔术师的功能至关重要。
我会很感激有关如何编写一个“了解”Magic 中的构造函数的 Roslyn 分析器的任何指导。
【问题讨论】: