首页 > 解决方案 > 如何使用属性来影响 C# 中的函数?

问题描述

根据C# attribute 中的文档,似乎在 C# 中,Attribute 只能用作编译时元数据存储。您必须使用反射来操作它。

...

public class AnimalTypeAttribute : Attribute {
    // The constructor is called when the attribute is set.
    public AnimalTypeAttribute(Animal pet) {
        thePet = pet;
    }

    // Keep a variable internally ...
    protected Animal thePet;

    // .. and show a copy to the outside world.
    public Animal Pet {
        get { return thePet; }
        set { thePet = value; }
    }
}

...

class DemoClass {
    static void Main(string[] args) {
        AnimalTypeTestClass testClass = new AnimalTypeTestClass();
        Type type = testClass.GetType();
        // Iterate through all the methods of the class.
        foreach(MethodInfo mInfo in type.GetMethods()) {
            // Iterate through all the Attributes for each method.
            foreach (Attribute attr in
                Attribute.GetCustomAttributes(mInfo)) {
                // Check for the AnimalType attribute.
                if (attr.GetType() == typeof(AnimalTypeAttribute))
                    Console.WriteLine(
                        "Method {0} has a pet {1} attribute.",
                        mInfo.Name, ((AnimalTypeAttribute)attr).Pet);
            }

        }
    }
}

但是,我注意到FlagsAttribute没有添加任何变量。虽然,它已经操纵/拦截了 ToString() 的输出(我猜)。FlagsAttribute 如何做到这一点?如何模仿行为,或影响自定义属性中的某些功能?

[FlagsAttribute] 
enum MultiHue : short {
   None = 0,
   Black = 1,
   Red = 2,
   Green = 4,
   Blue = 8
};

...

Console.WriteLine( "{0,3} - {1:G}", 3, (MultiHue)3); // output 3 - Black, Red

标签: c#attributes

解决方案


阅读代码Enum.cs,我们可以看到该ToString方法调用InternalFormat

public override string ToString()
{
    // Returns the value in a human readable format.  For PASCAL style enums who's value maps directly the name of the field is returned.
    // For PASCAL style enums who's values do not map directly the decimal value of the field is returned.
    // For BitFlags (indicated by the Flags custom attribute): If for each bit that is set in the value there is a corresponding constant
    // (a pure power of 2), then the OR string (ie "Red, Yellow") is returned. Otherwise, if the value is zero or if you can't create a string that consists of
    // pure powers of 2 OR-ed together, you return a hex value

    // Try to see if its one of the enum values, then we return a String back else the value
    return InternalFormat((RuntimeType)GetType(), ToUInt64()) ?? ValueToString();
}

private static string InternalFormat(RuntimeType eT, ulong value)
{
    Debug.Assert(eT != null);

    // These values are sorted by value. Don't change this
    TypeValuesAndNames entry = GetCachedValuesAndNames(eT, true);

    if (!entry.IsFlag) // Not marked with Flags attribute
    {
        return Enum.GetEnumName(eT, value);
    }
    else // These are flags OR'ed together (We treat everything as unsigned types)
    {
        return InternalFlagsFormat(eT, entry, value);
    }
}

InternalFormat调用GetCachedValuesAndNames访问信息缓存。

在这个GetCachedValuesAndNames方法中,我们可以看到它检查是否FlagsAttribute已定义 ( bool isFlags = enumType.IsDefined(typeof(FlagsAttribute), inherit: false);):

private static TypeValuesAndNames GetCachedValuesAndNames(RuntimeType enumType, bool getNames)
{
    TypeValuesAndNames entry = enumType.GenericCache as TypeValuesAndNames;

    if (entry == null || (getNames && entry.Names == null))
    {
        ulong[] values = null;
        string[] names = null;
        GetEnumValuesAndNames(
            enumType.GetTypeHandleInternal(),
            JitHelpers.GetObjectHandleOnStack(ref values),
            JitHelpers.GetObjectHandleOnStack(ref names),
            getNames);
        bool isFlags = enumType.IsDefined(typeof(FlagsAttribute), inherit: false);

        entry = new TypeValuesAndNames(isFlags, values, names);
        enumType.GenericCache = entry;
    }

    return entry;
}

所以它确实使用反射来确定是否FlagsAttribute存在,并相应地调整ToString结果。


推荐阅读