【问题标题】:Add intellisense when writing lambda's for an Action<dynamic>在为 Action<dynamic> 编写 lambda 时添加智能感知
【发布时间】:2014-01-17 19:18:49
【问题描述】:

这个问题与this other question有关。

我有以下方法:

public static T GetNewData<T>(params Action<dynamic>[] actions) where T : class, new()
{
    dynamic dynamicData = new DeepObject();

    foreach (var action in actions)
    {
        action(dynamicData);
    }

    return Converter.Convert<T>(dynamicData);
}

此方法的用户将包括较少的技术人员,甚至是非开发人员,因此更容易编写对该方法的调用越好。我现在的症结是,通过使用Action&lt;dynamic&gt; 作为参数类型,没有向用户提供智能感知。在上下文中,我知道智能感知应该表现得好像动态实际上是 T。

他们的方法也是我可以选择的一种方式:告诉 Visual Studio 使用类型 T 进行智能感知或将参数更改为 Action&lt;T&gt; 并以某种方式以编程方式将其更改为 Action&lt;dynamic&gt; 或 Action&lt;DeepObject&gt; 以便调用它会成功吗?

编辑:为了澄清,我用于 T 的类型不是 DeepObject 类型,它们不继承任何标准接口,DeepObject 的使用是允许设置嵌套类型而无需用户需要在每个级别显式实例化。这是添加动态和 DeepObject 代码之前的原始用法:

ExampleDataFactory.GetNewData<ServicesAndFeaturesInfo>(
    x => x.Property1 = ExampleDataFactory.GetNewData<Property1Type>(),
    x => x.Property1.Property2 = ExampleDataFactory.GetNewData<Property2Type>(),
    x => x.Property1.Property2.Property3 = ExampleDataFactory.GetNewData<Property3Type>(),
    x => x.Property1.Property2.Property3.Property4 = true);

这是现在的样子:

ExampleDataFactory.GetNewData<ServicesAndFeaturesInfo>(
    x => x.Property1.Property2.Property3.Property4 = true);

编辑:这是基于 nmclean 回答的完全实施的解决方案

public static DataBuilder<T> GetNewData<T>() where T : class, new()
{
    return new DataBuilder<T>();
}

DataBuilder 类:

public class DataBuilder<T>
{
    public readonly T data;

    public DataBuilder()
    {
        data = Activator.CreateInstance<T>();
    }

    public DataBuilder(T data)
    {
        this.data = data;
    }

    public DataBuilder<T> SetValue<T2>(Expression<Func<T, T2>> expression, T2 value)
    {
        var mExpr = GetMemberExpression(expression);

        var obj = Recurse(mExpr);
        var p = (PropertyInfo)mExpr.Member;
        p.SetValue(obj, value); 
        return this;
    }

    public T Build()
    {
        return data;
    }

    public object Recurse(MemberExpression expr)
    {
        if (expr.Expression.Type != typeof(T))
        {
            var pExpr = GetMemberExpression(expr.Expression);
            var parent = Recurse(pExpr);

            var pInfo = (PropertyInfo) pExpr.Member;
            var obj = pInfo.GetValue(parent);
            if (obj == null)
            {
                obj = Activator.CreateInstance(pInfo.PropertyType);
                pInfo.SetValue(parent, obj);
            }

            return obj;
        }
        return data;
    }

    private static MemberExpression GetMemberExpression(Expression expr)
    {
        var member = expr as MemberExpression;
        var unary = expr as UnaryExpression;
        return member ?? (unary != null ? unary.Operand as MemberExpression : null);
    }

    private static MemberExpression GetMemberExpression<T2>(Expression<Func<T, T2>> expr)
    {
        return GetMemberExpression(expr.Body);
    }
}

用法:

ExampleDataFactory.GetNewData<ServicesAndFeaturesInfo>()
            .SetValue(x=> x.Property1.EnumProperty, EnumType.Own)
            .SetValue(x=> x.Property2.Property3.Property4.BoolProperty, true)
            .Build();

【问题讨论】:

  • 你为什么使用Action&lt;dynamic&gt;作为你的参数类型?
  • @ledbutter 我的编辑可能会更清楚地说明这一点。

标签: c# .net generics dynamic


【解决方案1】:

不要使用Action&lt;dynamic&gt;,使用Action&lt;T&gt;和方法的约束where T:DeepObject。用户将获得使用强类型对象的智能和能力:

public static DeepObject GetNewData<T>(params Action<T>[] actions) 
 where T : DeepObject, //restrict user only inheritors of DeepObject 
           new()       //and require constructor
{
    var data = new T(); 

    foreach (var action in actions)
    {
        action(data); 
    }

    return data; 
}

【讨论】:

  • 对不起,我不太清楚 T 是什么,我添加了一个编辑,希望能更好地解释情况。
【解决方案2】:

用户是否需要访问未知属性或添加新属性?如果不是,使用动态对象似乎是倒退了一步。如果您想要的语法确实编译为 Action&lt;T&gt;,我认为您应该这样声明它,然后使用 LINQ 表达式 API 来决定如何解释代码。

不幸的是,尽管诸如赋值之类的语句是 part of the API,但 C# 不支持将它们转换为表达式树。这是不允许的:

public static T GetNewData<T>(params Expression<Action<T>>[] actions)
                where T : class, new() {
    ...
}

...

ExampleDataFactory.GetNewData<ServicesAndFeaturesInfo>(
    x => x.Property1.Property2.Property3.Property4 = true);

仅支持具有返回值的单行表达式。所以我认为你能做的最好的事情是这样的:

public class Assignment<T> {
    public readonly Expression Expression;
    public readonly object Value;

    public Assignment(Expression<Func<T, object>> expression, object value) {
        Expression = expression;
        Value = value;
    }
}

...

public static T GetNewData<T>(params Assignment<T>[] assignments)
                where T : class, new() {
    var data = Activator.CreateInstance<T>();

    foreach (var assignment in assignments) {
        // todo:
        // - pull property names from assignment.Expression
        // - initialize nested properties / assign to assignment.Value
    }

    return data;
}


...

ExampleDataFactory.GetNewData<ServicesAndFeaturesInfo>(
    new Assignment<ServicesAndFeaturesInfo>(
        x => x.Property1.Property2.Property3.Property4, true));

从链式属性访问的表达式树中获取属性名称并不太复杂。 Here is one implementation.

当然,new Assignment&lt;ServicesAndFeaturesInfo&gt;(...) 丑陋且重复,所以也许可以将其重组为这样的:

var newData = ExampleDataFactory.NewData<ServicesAndFeaturesInfo>();
newData.Add(x => x.Property1.Property2.Property3.Property4, true);
newData.Add(...);
...
newData.Get();

【讨论】:

  • 这个解决方案看起来是最接近我想要的解决方案。由于我无法修改将要返回的类,因此构建器模式的实现会稍微困难一些。
  • @JustinM 你不需要修改类——我在想NewData&lt;T&gt;返回一些DataBuilder&lt;T&gt;,DataBuilder&lt;T&gt;.Get返回内部T对象。
  • 啊,好吧,这正是我最终实现它的方式。对于那些感兴趣的人,我会将大部分代码放在我的问题底部。
猜你喜欢
  • 2016-11-23
  • 2013-09-23
  • 1970-01-01
  • 1970-01-01
  • 2015-06-13
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
  • 2012-02-09
相关资源
最近更新 更多