Кога се изпълнява конструкторът на потребителски атрибут?

Кога се изпълнява? Изпълнява ли се за всеки обект, към който го прилагам, или само веднъж? Може ли да прави нещо или действията му са ограничени?


person devoured elysium    schedule 22.07.2009    source източник


Отговори (4)


Кога се изпълнява конструкторът? Опитайте го с проба:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Creating MyClass instance");
        MyClass mc = new MyClass();
        Console.WriteLine("Setting value in MyClass instance");
        mc.Value = 1;
        Console.WriteLine("Getting attributes for MyClass type");
        object[] attributes = typeof(MyClass).GetCustomAttributes(true);
    }

}

[AttributeUsage(AttributeTargets.All)]
public class MyAttribute : Attribute
{
    public MyAttribute()
    {
        Console.WriteLine("Running constructor");
    }
}

[MyAttribute]
class MyClass
{
    public int Value { get; set; }
}

И какъв е резултатът?

Creating MyClass instance
Setting value in MyClass instance
Getting attributes for MyClass type
Running constructor

И така, конструкторът на атрибути се изпълнява, когато започнем да изследваме атрибута. Имайте предвид, че атрибутът се извлича от типа, а не от екземпляра на типа.

person Fredrik Mörk    schedule 22.07.2009
comment
Имайте предвид, че конструкторът се изпълнява многократно, ако извикате GetCustomAttributes няколко пъти. - person Wim Coenen; 23.07.2009
comment
Да, това е добре да се отбележи; не се извършва кеширане за атрибутите (за разлика от обектите тип, които, доколкото си спомням, се кешират, докато приложението работи). - person Fredrik Mörk; 23.07.2009
comment
Някой знае ли защо атрибутните обекти не са кеширани? - person Matt Howells; 23.07.2009
comment
@Matt: Не знам, но моето предположение би било, че Type обектът се използва достатъчно често, за да е интересно да се направи инвестиция в кеширането им, докато това може да не е случаят с Attributes. Но аз само спекулирам. - person Fredrik Mörk; 23.07.2009
comment
Можете също да добавите код в конструктора на вашия атрибут, например, за да промените стойностите според контекста. ако атрибутът е кеширан, не можете да направите това. - person Guillaume; 10.03.2010
comment
Ами... благодаря. Спуках малко балона ми, но все пак полезно. - person StingyJack; 04.01.2012
comment
За да кеширате CustomAttribute данните за атрибут, който винаги е свързан с определени Type, дефинирайте персонализиран TypeDelegator (или йерархия от TypeDelegator класове, която е паралелна на йерархията на наследяване на вашите действителни класове). TypeDelegator е разумно място за поставяне на персонализирана Type информация, тъй като наследява от Type. Това разширява съответните екземпляри на CLR Type с допълнителни метаданни за време на изпълнение на поле, метод и свойство въз основа на вашите потребителски данни за атрибути. След това вашето приложение надгражда всеки Type до кеширан TypeDelegator само веднъж и след това винаги се занимава с последния. - person Glenn Slayden; 18.08.2020

Конструкторът се изпълнява всеки път, когато се извика GetCustomAttributes или всеки път, когато друг код извиква конструктора директно (не че има основателна причина да се направи това, но не е и невъзможно).

Имайте предвид, че поне в .NET 4.0 екземплярите на атрибута не са кеширани; нов екземпляр се конструира всеки път, когато се извика GetCustomAttributes:

[Test]
class Program
{
    public static int SomeValue;

    [Test]
    public static void Main(string[] args)
    {
        var method = typeof(Program).GetMethod("Main");
        var type = typeof(Program);

        SomeValue = 1;

        Console.WriteLine(method.GetCustomAttributes(false)
            .OfType<TestAttribute>().First().SomeValue);
        // prints "1"

        SomeValue = 2;

        Console.WriteLine(method.GetCustomAttributes(false)
            .OfType<TestAttribute>().First().SomeValue);
        // prints "2"

        SomeValue = 3;

        Console.WriteLine(type.GetCustomAttributes(false)
            .OfType<TestAttribute>().First().SomeValue);
        // prints "3"

        SomeValue = 4;

        Console.WriteLine(type.GetCustomAttributes(false)
            .OfType<TestAttribute>().First().SomeValue);
        // prints "4"

        Console.ReadLine();
    }
}

[AttributeUsage(AttributeTargets.All)]
class TestAttribute : Attribute
{
    public int SomeValue { get; private set; }

    public TestAttribute()
    {
        SomeValue = Program.SomeValue;
    }
}

Не е най-добрата идея атрибутите да се държат по този начин, разбира се. Най-малкото имайте предвид, че GetCustomAttributes не е документирано да се държи по този начин; всъщност какво се случва в горната програма не е посочено в документацията.

person Roman Starkov    schedule 06.08.2012
comment
Това е начинът, по който трябваше да се държи (глаголът get предполага действие). GetCustomAttributes ТРЯБВА да връща свежи копия всеки път. Това е единственият начин да се гарантира, че кодът, изпълняван в конструктора на атрибута, изследва подходящ контекст. С динамичните сглобки, прихващането и всичко останало, не може да се каже какво може да се е променило след последното обаждане. Може дори да върне различен набор от атрибути въз основа на настройка в реално време. - person Paul Easter; 07.02.2015
comment
@PaulEaster Съгласен съм, че това поведение е ясно проектирано, което позволява някои интересни конструктори на атрибути. Но когато имах по-малко опит с .NET, изглеждаше интуитивно, че тъй като атрибутите са изпечени в метаданните на асемблирането, те не могат да се променят по време на изпълнение на програмата (но се оказа, че могат). Така че такива конструктори със сигурност могат да изненадат някого неприятно. - person Roman Starkov; 07.02.2015

Задайте точка на прекъсване на дебъгер вътре в конструктор на атрибути и напишете някакъв код за отразяване, който чете тези атрибути. Ще забележите, че атрибутните обекти няма да бъдат създадени, докато не бъдат върнати от API за отражение. Атрибутите са за клас. Те са част от метаданните.

Разгледайте това:

Program.cs

using System;
using System.Linq;
[My(15)]
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Program started");
        var ats =
            from a in typeof(Program).GetCustomAttributes(typeof(MyAttribute), true)
            let a2 = a as MyAttribute
            where a2 != null
            select a2;

        foreach(var a in ats)
            Console.WriteLine(a.Value);

        Console.WriteLine("Program ended");
        Console.ReadLine();
    }
}

MyAttribute.cs

using System;
[AttributeUsage(validOn : AttributeTargets.Class)]
public class MyAttribute : Attribute
{
    public MyAttribute(int x)
    {
        Console.WriteLine("MyAttribute created with {0}.", x);
        Value = x;
    }

    public int Value { get; private set; }    
}

Резултат

Program started
MyAttribute created with 15.
15
Program ended

Но не се притеснявайте за производителността на конструкторите на атрибути. Те са най-бързата част от размисъла :-P

person Christian Klauser    schedule 22.07.2009
comment
по дяволите! Опитах се да направя това във VS 2010, но съм напълно изгубен без ReSharper XD - person Christian Klauser; 23.07.2009

Метаданните в изпълнимия файл или DLL се съхраняват:

  • Токен за метаданни, указващ конструктора за извикване
  • Аргументите

Когато стигна до този раздел от моята реализация на CLI, планирам да извикам отложено конструктора при първото извикване на GetCustomAttributes() за ICustomAttributeProvider. Ако се поиска конкретен тип атрибут, ще конструирам само онези, които са необходими за връщане на този тип.

person Sam Harwell    schedule 22.07.2009