【问题标题】:c# lambda expression + reflection questionsc# lambda 表达式 + 反射题
【发布时间】:2010-09-21 14:45:49
【问题描述】:

这主要用于教育目的。我正在尝试创建本示例中使用的 InputMapper 类:

var mapper = new InputMapper<SomeType>();
mapper.Map("some user input", someType => someType.IntProperty, "Input was not an integer");
mapper.Map("some user input", someType => someType.BoolProperty, "Input was not a bool");

SomeType someTypeInstance = mapper.CreateInstance();

我的 InputMapper 类包含使用 Map() 方法创建的所有映射的集合。 CreateInstance() 将遍历试图转换用户输入并将其分配给 lambda 表达式中使用的属性的映射。在循环过程中,它将保存抛出的任何 FormatExceptions 的集合。

我的问题是:

  • InputMapper.Map()方法中,lambda参数类型应该是什么?
  • 在 InputMapper.CreateInstance() 方法中,如何尝试设置我创建的 T 实例的属性?

谢谢!

更新

博士。 Skeet 询问有关我的意图的更多信息。

InputMapper 类将用于将用户输入分配给任何对象的成员,负责将用户输入转换为属性类型。类的接口可以从上面的例子中推断出来。

更新 2

在牵手之后,乔恩和丹将我带到了那里。你能提出改进建议吗?这是我所拥有的:http://pastebin.com/RaYG5n2h

【问题讨论】:

  • 这取决于 -- someType.IntProperty / BoolProperty 的类型是什么?
  • 假设“sometType”实际上应该读作“someType”。
  • @david - int 和 bool 分别...
  • 有什么特别的理由想要并使用 lambda 作为参数,而不仅仅是一个枚举来指示类型?
  • @Josh - 该参数不用于指示类型。它用于指示要尝试使用 Map() 方法提供的用户输入设置的属性。

标签: c# .net reflection lambda


【解决方案1】:

对于您的第一个问题,Map 方法可能应该是通用的。例如:

public class InputMapper<TSource> where TSource : new()
{
    public void Map<TResult>(string input,
                             Expression<Func<TSource, TResult>> projection,
                             string text)
    {
        ...
    }
}

现在值得注意的是,您的 lambda 表达式表示属性 getters,但可能您想调用属性 setters。这意味着您的代码将不是编译时安全的——例如,可能存在一个只读的映射属性。此外,没有什么可以将 lambda 表达式限制为 仅 引用属性。它可以做任何事情。您必须在执行时设置防范措施。

一旦你已经找到了属性设置器,你只需要使用new TSource() 创建一个TSource 的实例(注意上面TSource 的构造函数约束),然后执行适当的转换并调用属性设置器。

如果没有更多关于你想要做什么的细节,恐怕很难给出更详细的答案。

编辑:计算属性的代码如下所示:

var memberExpression = projection.Body as MemberExpression;
if (memberExpression == null)
{
    throw new ArgumentException("Lambda was not a member access");
}
var propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo == null)
{
    throw new ArgumentException("Lambda was not a property access");
}
if (projection.Parameters.Count != 1 ||
    projection.Parameters[0] != memberExpression.Expression)
{
    throw new ArgumentException("Property was not invoked on parameter");
}
if (!propertyInfo.CanWrite)
{
    throw new ArgumentException("Property is read-only");
}
// Now we've got a PropertyInfo which we should be able to write to - 
// although the setter may be private. (Add more tests for that...)
// Stash propertyInfo appropriately, and use PropertyInfo.SetValue when you 
// need to.

【讨论】:

  • 我在我的问题中提供了更多信息,我正在寻找如何为投影参数描述的属性分配值的示例代码。
  • 在尝试编译时出现“错误 3 找不到类型或命名空间名称 'TResult'(您是否缺少 using 指令或程序集引用?)”。 TSource 也一样。
  • @Ronnie:我怀疑你没有像我那样声明方法 - TResult 和 TSource 是类型参数; TResult是方法的类型参数,TSource是类型的类型参数。
  • 你是对的。我快到了。现在只是得到 TResult 的错误。这是我的代码:pastebin.com/d3khznP3
  • @Ronnie:您不能将List&lt;InputMapping&lt;TProperty&gt;&gt; 作为成员字段,因为没有一致的TProperty(不同的属性将属于不同的类型)。您必须使 InputMapping&lt;TProperty&gt; 从一些非通用基础继承,并使您的字段成为 List&lt;InputMapping&gt;(或类似的东西)。
【解决方案2】:
public void Map<TValue>(string input, Expression<Func<T, TValue>> property, string message);

那么 CreateInstance 可能如下所示:

public T CreateInstance() 
{
    T result = new T();
    foreach (var lambda in map) 
    {
        ((PropertyInfo)((MemberExpression)lambda.Body).Member).SetValue(result, null, propertyValueForLambdaThatYouStored);  
    }
    return result;
}

如果 lambda 表达式不是属性引用,您可以添加一些检查以引发更好的异常。

【讨论】:

  • 谢谢!我会试一试。我很高兴有人理解我。
【解决方案3】:

更新:请参阅here,了解我认为您正在尝试做的工作示例。

(更新 2:看起来您在我发布此内容前不久就已经弄清楚了。很好!无论如何,我会把它留在那里以供参考,即使它不是很有效实施。)

下面是一个演示程序。

class CustomType
{
    public int Integer { get; set; }
    public double Double { get; set; }
    public bool Boolean { get; set; }
    public string String { get; set; }

    public override string ToString()
    {
        return string.Format("int: {0}, double: {1}, bool: {2}, string: {3}", Integer, Double, Boolean, String);
    }
}

class Program
{
    public static void Main(string[] args)
    {
        var mapper = new InputMapper<CustomType>();

        mapper.Map("10", x => x.Integer, "Unable to set Integer property.");
        mapper.Map("32.5", x => x.Double, "Unabled to set Double property.");
        mapper.Map("True", x => x.Boolean, "Unable to set Boolean property.");
        mapper.Map("Hello world!", x => x.String, "Unable to set String property.");

        var customObject = mapper.Create();

        Console.WriteLine(customObject);

        Console.ReadKey();
    }
}

输出:

int: 10, double: 32.5, bool: True, string: Hello world!

听起来您想像这样定义Map 函数:

class InputMapper<T>
{
    public void Map<TProperty>(string input,
                               Expression<Func<T, TProperty>> propertyExpression,
                               string errorMessage);
}

那么大概你想要做的是根据用户输入从propertyExpression 中找出你想要设置的属性。对吗?

不过,我完全不明白为什么你不想这样定义它:

class InputMapper<T>
{
    public void Map<TProperty>(string input,
                               Action<TProperty> propertySetter,
                               string errorMessage);
}

然后用法看起来像:

mapper.Map<int>(
    "some user input",
    value => someType.IntProperty = value,
    "Input was not an integer"
);

(请注意,您的 Map&lt;TProperty&gt; 函数必须在内部处理将用户输入解析为适当类型的问题,可能使用像 Convert.ChangeType 这样的简单方法。)

【讨论】:

  • 看起来您接受操作参数的建议使调用代码负责将输入分配给属性。我不想那样做。那是 InputMapper 的责任。我只想告诉类我希望它创建的类型,尝试使用什么用户输入来设置属性,以及稍后将显示给用户的错误消息。错误消息部分与我真正遇到的问题无关,我不应该将其包含在我的问题中。
  • @Ronnie:好的,很公平;然后去我的第一个建议。您当然可以从Expression&lt;Func&lt;T, TProperty&gt;&gt; 转到属性设置器;但是正如乔恩在他的回答中指出的那样,如果调用代码给你的不是属性表达式,你必须首先决定你想做什么,以及当事实证明传递给你的属性无法设置时该怎么做。只是一些需要注意的事情。
  • 非常感谢。希望我可以将 2 个答案标记为已接受。乔恩让我快一点。
猜你喜欢
  • 2010-12-22
  • 1970-01-01
  • 2017-04-22
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多