Подтвердить что ты не робот

Какой смысл указывать AllowMultiple = false в абстрактном классе атрибута?

В недавнем question атрибутах MVC кто-то спросил, могут ли использование атрибутов HttpPost и HttpDelete в методе действий разрешать либо тип запроса, либо запросы (поскольку он не может одновременно быть как Почтой, так и Удалить). Я заметил, что ActionMethodSelectorAttribute, из которого вызывается HttpPostAttribute и HttpDeleteAttribute, украшен

[AttributeUsage(AttributeTargets.Method,
                AllowMultiple = false,
                Inherited = true)]

Я ожидал, что из-за этого я не допущу, чтобы оба HttpPost и HttpDelete были на одном и том же методе, но компилятор не жалуется. Мое ограниченное тестирование говорит мне, что использование атрибута в базовом классе просто игнорируется. AllowMultiple, по-видимому, только запрещает применение двух атрибутов одинакового к методу/классу и, похоже, не рассматривает вопрос о том, происходят ли эти атрибуты из того же класса, который настроен так, чтобы не допускать кратных значений. Более того, использование атрибута в базовом классе даже не мешает вам изменить использование атрибута на производном классе. В этом случае, какой смысл даже устанавливать значения в базовом классе атрибутов? Является ли это просто консультативным или я упускаю что-то принципиальное в том, как они работают?

FYI - оказывается, что использование обоих в основном исключает этот метод из когда-либо рассматриваемого. Атрибуты оцениваются независимо, и один из них всегда указывает, что метод недействителен для запроса, поскольку он не может одновременно быть как сообщением, так и удалением.

4b9b3361

Ответ 1

Настройка AllowMultiple в базовом атрибуте по существу устанавливает значение по умолчанию для всех атрибутов, которые выводятся из него. Если вы хотите, чтобы все атрибуты, полученные из базового атрибута, разрешали несколько экземпляров, вы можете сохранить дублирование, применив атрибут [AttributeUsage] к этому эффекту к базовому атрибуту, избегая необходимости делать то же самое со всеми производными атрибутами.

Например, предположим, что вы хотели разрешить это:

public abstract class MyAttributeBase : Attribute
{
}

public sealed class FooAttribute : MyAttributeBase
{
}

public sealed class BarAttribute : MyAttributeBase
{
}

[Foo]
[Foo]
[Bar]
[Bar]
public class A
{
}

Как бы то ни было, это порождает ошибку компилятора, поскольку по умолчанию пользовательские атрибуты не разрешают несколько экземпляров. Теперь вы можете применить [AttribteUsage] к [Foo] и [Bar] следующим образом:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class FooAttribute : MyAttributeBase
{
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class BarAttribute : MyAttributeBase
{
}

Но вместо этого вы можете просто применить его к базовому атрибуту:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public abstract class MyAttributeBase : Attribute
{
}

public sealed class FooAttribute : MyAttributeBase
{
}

public sealed class BarAttribute : MyAttributeBase
{
}

Оба подхода имеют одинаковый прямой эффект (допускаются множественные экземпляры как [Foo], так и [Bar]), но второй подход также оказывает косвенное влияние на то, что любые другие атрибуты, полученные из [MyAttribute], теперь разрешают несколько экземпляров, если только они имеют свой собственный [AttributeUsage], который отменяет эту настройку.

Ответ 2

AllowMultiple разрешает/запрещает использование определенного атрибута более одного раза. Это не влияет на то, могут ли быть объединены другие атрибуты.

Так, например, если у вас есть ObfuscationAttribute, который контролирует, включено или отключено переименование метода, вы не хотите, чтобы пользователи могли это сделать:

[Obfuscation("DisableRenaming")]
[Obfuscation("EnableRenaming")]
void MyMethod()
{
}

В этом случае Obfuscation не может быть как включен, так и отключен, поэтому вы должны использовать AllowMultiple = false, чтобы гарантировать, что этот метод будет отмечен только один раз с помощью этого конкретного атрибута.

То, что вы могли гипотетически сделать, в вашем взаимоисключающем случае, - это использовать один атрибут HttpSettings, который взял паранонтер, указывающий, применяется ли он к сообщению "Удалить" или "Удалить". Это может быть AllowMultiple = false, чтобы обеспечить взаимную эксклюзивность параметров.

Ответ 3

Сделайте короткий тест:

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Reflection;

namespace TestAttrs {
    public abstract class BaseAttribute : Attribute { 
        public string text; 
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class MultipleInheritedAttribute : BaseAttribute {  }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
    public class MultipleNonInheritedAttribute : BaseAttribute {  }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class SingleInheritedAttribute : BaseAttribute {  }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class SingleNonInheritedAttribute : BaseAttribute {  }

    public class BaseClass {
        [MultipleInherited(text = "MultipleInheritedBase")]
        [MultipleNonInherited(text = "MultipleNonInheritedBase")]
        [SingleInherited(text = "SingleInheritedBase")]
        [SingleNonInherited(text = "SingleNonInheritedBase")]
        public virtual void Method() { ; }
    }

    public class DerivedClass : BaseClass {
        [MultipleInherited(text = "MultipleInheritedDerived")]
        [MultipleNonInherited(text = "MultipleNonInheritedDerived")]
        [SingleInherited(text = "SingleInheritedDerived")]
        [SingleNonInherited(text = "SingleNonInheritedDerived")]
        public override void Method() {
            base.Method();
        }
    }

    [TestClass]
    public class AttributesTest {
        [TestMethod]
        public void TestAttributes() {
            MemberInfo mi = typeof(DerivedClass).GetMember("Method")[0];
            object[] attrs = mi.GetCustomAttributes(true);

            string log = "";
            foreach(BaseAttribute attr in attrs) {
                log += attr.text+"|";
            }
            Assert.AreEqual("MultipleInheritedDerived|SingleInheritedDerived|SingleNonInheritedDerived|MultipleNonInheritedDerived|MultipleInheritedBase|", log);
        }
    }
}

Как вы можете видеть, если атрибут отмечен Inherted=true, то он будет возвращен для производных классов, но если унаследованный метод отмечен тем же атрибутом - он будет подавлен, если AllowMultiple=false. Итак - в нашем тесте строка журнала содержит как "MultipleInheritedDerived", так и "MultipleInheritedBase", но не "SingleInheritedBase".

Итак, отвечая на ваш вопрос - какой смысл? Эта комбинация позволяет вам иметь базовый контроллер с виртуальным методом, который вы можете переопределить, не беспокоясь об атрибуте (он будет взят из базового метода), но в то же время, чтобы иметь возможность переопределить его, если хотите. HttpPostAttribute не является хорошим примером, поскольку он не имеет параметров, но другие атрибуты могут извлечь выгоду из таких настроек.

Кроме того, обратите внимание, что атрибуты, потребляющие код:

       object[] attrs = mi.GetCustomAttributes(true);

указывает, что он заинтересован в унаследованных атрибутах. Если пишите

       object[] attrs = mi.GetCustomAttributes(false);

тогда результат будет содержать 4 атрибута, независимо от их настроек использования. Таким образом, разработчик может игнорировать настройку использования Inherited.