1

First I'll describe what I'm trying to achieve.

I want to create a method that grabs a property name and its value for logging purposes, so I have this:

public void Log<TPropertySource, TProperty>(Expression<Func<TPropertySource, object>> property, TProperty initialValue, TProperty changedValue){...}

Now this requires me to specify the property's type, which is .. meh, because in theory I can pull that from an expression; however expression needs to return an object to accommodate for all the possible types the property can have.

I'm thinking about just having overloads for BCL's most used value types, and an overload with object for everything else, e.g.

public void Log<TPropertySource>(Expression<Func<TPropertySource, string>> property, string initialValue, string changedValue){...}

public void Log<TPropertySource>(Expression<Func<TPropertySource, int>> property, int initialValue, int changedValue){...}

but its not ideal either, because I'll end up with like a dozen overloads

So basically I wonder if there is a better (lazier) way to do it ?

One more question: why do I get no intellisense on Log<TPropertySource>(Expression<Func<TPropertySource, int>> property, int initialValue, int changedValue) ? If I type logger.Log<A>(x => x.Age, 1, 2); - it compiles fine, but intellisense just won't kick in.

Evgeni
  • 3,341
  • 7
  • 37
  • 64
  • Type inference should infer both types. – SLaks Sep 08 '13 at 21:10
  • I don't get it, why can't you have `public void Log(Expression> property, TProperty initialValue, TProperty changedValue)`? You say "expression needs to return an object to accommodate for all the possible types the property can have", but there are no restrictions on which types `TProperty` can take, so all property types should work. –  Sep 08 '13 at 21:56
  • @SLaks Would be nice, but it doesn't. At least, I can't get it to work. – Evgeni Sep 09 '13 at 01:42
  • @hvd I can, but I'll have to call it like Log(x=>x.Name, "Bob", "Bill"); I'm trying to avoid having to specify the 2nd generic parameter (string in this case). – Evgeni Sep 09 '13 at 01:44
  • @Eugene: You should be able to write `Log(x => x.Name, ...)`, and the compiler should infer both parameters. – SLaks Sep 09 '13 at 02:51
  • I missed that because the `<` characters were getting lost in the question (interpreted as invalid HTML), causing your question to not show how you're calling `Log`. @SLaks That can't work, there is no way to infer the type of `x`. –  Sep 09 '13 at 06:56
  • 2
    @Eugene Would a changed API be okay? `logger.Property(x => x.Age).Log(1, 2);` should be doable. –  Sep 09 '13 at 06:58
  • @hvd Nah, this still would require to define a 2nd type; but I think I'm asking for impossible. – Evgeni Sep 10 '13 at 02:44

1 Answers1

0

The easiest way would be to just specify property source type with lambda expression: logger.Log((User _) => _.Age, 1, 2) so all types will be inferred.

Another way is to separate type parameters, so they can be inferred or specified one by one. Here is the general pattern:

Let's suppose we have some method M with n type parameters


class C
{
    public void M<T1, T2, ..., Tn>(...) {}
}

We have to either infer all of them or specify all of them explicitly. In order to specify/infer them independently will have to change API to provide intermediate methods and types for all type parameters:


class C
{
    public T1Binder<T1> M1<T1>(...) {}
}

class T1Binder<T1>
{
    public T2Binder<T2> M2<T2>(...) {}
}

...

class TFinalBinder<T(n - 1)>
{
    public void MFinal<Tn>(...) {}
}

now we can have a chain of calls where every type parameter can be independently inferred or specified:


c.M1<T1>().M2(t2Value)...MFinal(tnValue);

For your case it can look like this:


public class Logger
{
    public PropertyLogger<TPropertySource> PropertyOf<TPropertySource>()
    {
        return new PropertyLogger<TPropertySource>();
    }
}

public class PropertyLogger<TPropertySource>
{
    public void Log<TProperty>(
        Expression<Func<TPropertySource, object>> property,
        TProperty initialValue,
        TProperty changedValue)
    {
    }
}

Which can be used with e.g. logger.PropertyOf<SomeType>().Log(_ => _.Age, 1, 2). This API is obviously more complicated to implement and use, but it can be handy when you need to process more than 1 property:


logger.PropertyOf<SomeType>()
    .Log(_ => _.Age, 1, 2)
    .Log(_ => _.Name, "Mike", "Adam");

And surely you can combine both approaches so API user can choose whichever better suits his particular case.

Konstantin Oznobihin
  • 5,234
  • 24
  • 31