Во время компиляции №.
Во время выполнения вы можете проверить это с помощью статического инициализатора, который выдает, если этот инвариант нарушен, хотя это будет считаться очень плохим стилем, это будет безопасно в том смысле, что никакой код не может быть выполнен, пока инвариант не удерживается. *
Если вы думаете о расширяемости, присущей .Net, даже если вы можете проверить это во время компиляции, представьте:
скомпилируйте dll A с
public class Foo
{
public int Property1 {get;}
}
скомпилируйте dll B, ссылающуюся на A.dll с классом
public class Bar
{
[OnlyOneAllowedOnAnyPropertiesPerClass]
public int Property2 {get;}
}
тогда вы перекомпилируете A.dll с
public class Foo
{
[OnlyOneAllowedOnAnyPropertiesPerClass]
public int Property1 {get;}
}
И попытайтесь запустить этот новый A.dll со старым B.dll (они бинарно совместимы во всех других отношениях, так что это нормально)
Очевидно, что во время выполнения потребуется приложить немало усилий, чтобы проверить это, не говоря уже о том, что B может не загружаться в течение некоторого времени внезапно, делая один или оба из A и B «незаконными».
Поэтому не следует ожидать, что это когда-либо будет функциональностью, доступной в фреймворке.