That's not a good idea
It will be unreasonably difficult to do something like what you describe, and the result would not be maintainable even if you did manage to do it.
The difficulty stems from the restrictions on attribute parameters:
Attribute parameters are restricted to constant values of the
following types:
- Scalar types (bool, byte, char, short, int, long, float, and double)
string
System.Type
- enums
object
(which must be a constant value of one of the above types)
- One-dimensional arrays of any of the above types
It's obvious that the only way you could squeeze a predicate into any of the above types is by writing a string a-la SQL, e.g.
[Hide("foo = \"42\" && !bar")]
public object MyProperty { get; set; }
You would then need to parse this string at runtime, convert it to a machine-usable form and decide what the result would be. And even then it would be very easy to write an invalid predicate since the string is totally opaque to the compiler.
But there are alternatives
Your attempted solution is really trying to swim against the current -- attributes were not meant to encapsulate runtime behavior. Instead of doing this, why not simply have your serializable class implement a suitable interface? For example, you could start with the bog-standard
public interface IConditionalPropertySource
{
bool IsPropertyApplicable(string propertyName);
}
class Test : IConditionalPropertySource
{
public string SomeSetting { get; set; }
public bool IsPropertyApplicable(string propertyName)
{
switch (propertyName)
{
case "SomeSetting":return DateTime.Now.DayOfWeek == DayOfWeek.Friday;
default: return false;
}
}
}
This will do the job but it does have some drawbacks:
- Property names are not checked by the compiler; both the caller and the implementation of
IsPropertyApplicable
could make mistakes (e.g. simple misspelling) which would not be flagged.
- It's not immediately clear which properties are conditional and which are not just by looking at their declarations.
- The exact relationship between properties and conditions is somewhat hidden.
With compile-time safety too
If the above is not satisfactory, you can improve on it by eliminating the first two issues and improving on the third one for a small runtime cost. The idea is based on a well-known trick to provide compile-time safety when referring to property names: instead of specifying them as a string, specify them as member access expressions.
public interface IConditionalPropertySource<T>
{
bool IsPropertyApplicable(Expression<Func<T, object>> expr);
}
You can call the above as IsPropertyApplicable(o => o.SomeSetting)
and get "SomeSetting"
as a string at runtime with ((MemberExpression)expr.Body).Member.Name
. However, we don't really want to work with a bare string at any time because that would mean issue #1 above still exists.
So instead of this, we can create a dictionary that maps member access expressions to boolean functions and provide an equality comparer that replaces the default equality semantics for expressions (reference equality) with member name equality:
class Test : IConditionalPropertySource<Test>
{
// Your properties here:
public string SomeSetting { get; set; }
// This is the equality comparer used for the dictionary below
private class MemberNameComparer :
IEqualityComparer<Expression<Func<Test, object>>>
{
public bool Equals(
Expression<Func<Test, object>> lhs,
Expression<Func<Test, object>> rhs)
{
return GetMemberName(lhs).Equals(GetMemberName(rhs));
}
public int GetHashCode(Expression<Func<Test, object>> expr)
{
return GetMemberName(expr).GetHashCode();
}
private string GetMemberName(Expression<Func<Test, object>> expr)
{
return ((MemberExpression)expr.Body).Member.Name;
}
}
// A dictionary that maps member access expressions to boolean functions
private readonly IDictionary<Expression<Func<Test, object>>, Func<bool>>
conditions = new Dictionary<Expression<Func<Test, object>>, Func<bool>>
(new MemberNameComparer())
{
// The "SomeSetting" property is only visible on Wednesdays
{
self => self.SomeSetting,
() => DateTime.Now.DayOfWeek == DayOfWeek.Wednesday
}
};
// This implementation is now trivial
public bool IsPropertyApplicable(Expression<Func<Test, object>> expr)
{
return conditions[expr]();
}
}
This eliminates issue #1 (you can no longer misspell property names, the compiler will catch that) and it improves on #3 (properties and conditions are somewhat more visible). It still leaves issue #2 unaddressed: you can't tell if SomeProperty
is conditionally visible just by looking at its declaration.
However, you could expand the code to enforce this at runtime:
- Decorate conditionally visible properties with a custom attribute
- Inside the constructor, enumerate all properties of the class decorated with that attribute and all property names that can be derived from the dictionary keys
- Treat both enumerated collections as sets
- If the sets are not equal there is a mismatch between the properties that have been decorated and those that have conditional visibility logic defined; throw an exception