在C#中通过引用传递属性

yog*_*ear 214 c# properties pass-by-reference

我正在尝试做以下事情:

GetString(
    inputString,
    ref Client.WorkPhone)

private void GetString(string inValue, ref string outValue)
{
    if (!string.IsNullOrEmpty(inValue))
    {
        outValue = inValue;
    }
}
Run Code Online (Sandbox Code Playgroud)

这给了我一个编译错误.我认为我很清楚我想要实现的目标.基本上我想GetString将输入字符串的内容复制到WorkPhone属性Client.

是否可以通过引用传递属性?

Nat*_*lch 391

属性不能通过引用传递.您可以通过以下几种方法解决此限制.

1.返回值

string GetString(string input, string output)
{
    if (!string.IsNullOrEmpty(input))
    {
        return input;
    }
    return output;
}

void Main()
{
    var person = new Person();
    person.Name = GetString("test", person.Name);
    Debug.Assert(person.Name == "test");
}
Run Code Online (Sandbox Code Playgroud)

2.代表

void GetString(string input, Action<string> setOutput)
{
    if (!string.IsNullOrEmpty(input))
    {
        setOutput(input);
    }
}

void Main()
{
    var person = new Person();
    GetString("test", value => person.Name = value);
    Debug.Assert(person.Name == "test");
}
Run Code Online (Sandbox Code Playgroud)

3. LINQ表达式

void GetString<T>(string input, T target, Expression<Func<T, string>> outExpr)
{
    if (!string.IsNullOrEmpty(input))
    {
        var expr = (MemberExpression) outExpr.Body;
        var prop = (PropertyInfo) expr.Member;
        prop.SetValue(target, input, null);
    }
}

void Main()
{
    var person = new Person();
    GetString("test", person, x => x.Name);
    Debug.Assert(person.Name == "test");
}
Run Code Online (Sandbox Code Playgroud)

4.反思

void GetString(string input, object target, string propertyName)
{
    if (!string.IsNullOrEmpty(input))
    {
        prop = target.GetType().GetProperty(propertyName);
        prop.SetValue(target, input);
    }
}

void Main()
{
    var person = new Person();
    GetString("test", person, nameof(Person.Name));
    Debug.Assert(person.Name == "test");
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为解决方案3的一个更好的名称是Reflection. (29认同)
  • 在解决方案2中,第二个参数`getOutput`是不必要的. (9认同)
  • 喜欢这些例子。我发现这也是扩展方法的好地方:`code`public static string GetValueOrDefault(this string s, string isNullString) { if (s == null) { s = isNullString; 返回 s; } void Main(){ person.MobilePhone.GetValueOrDefault(person.WorkPhone); } (2认同)
  • 在解决方案 2 中,第二个参数 getOutput 是不必要的 - true 但我在 GetString 中使用它来查看我设置的值是什么。不知道如果没有这个参数该怎么做。 (2认同)
  • 使用 *reflection* AND *Linq 表达式* 的解决方案 3 非常优雅,并且很好地完成了这项工作。4 年后,仍然做得很好:) (2认同)
  • @GoneCodingGoodbye:但是效率最低的方法。使用反射简单地为属性分配值就像用大锤砸破螺母一样。同样,应该设置属性的方法“ GetString”显然被错误命名。 (2认同)
  • @TimSchmelter:效率是相对的。它始终是速度和可维护性之间的平衡。不要遭受过早优化的困扰!:) (2认同)

Fir*_*iro 25

没有重复财产

void Main()
{
    var client = new Client();
    NullSafeSet("test", s => client.Name = s);
    Debug.Assert(person.Name == "test");

    NullSafeSet("", s => client.Name = s);
    Debug.Assert(person.Name == "test");

    NullSafeSet(null, s => client.Name = s);
    Debug.Assert(person.Name == "test");
}

void NullSafeSet(string value, Action<string> setter)
{
    if (!string.IsNullOrEmpty(value))
    {
        setter(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • +1用于将名称`GetString`更改为`NullSafeSet`,因为前者在这里没有意义. (3认同)

Sve*_*ven 20

我使用ExpressionTree变体和c#7编写了一个包装器(如果有人感兴趣的话):

public class Accessor<T>
{
    private Action<T> Setter;
    private Func<T> Getter;

    public Accessor(Expression<Func<T>> expr)
    {
        var memberExpression = (MemberExpression)expr.Body;
        var instanceExpression = memberExpression.Expression;
        var parameter = Expression.Parameter(typeof(T));

        if (memberExpression.Member is PropertyInfo propertyInfo)
        {
            Setter = Expression.Lambda<Action<T>>(Expression.Call(instanceExpression, propertyInfo.GetSetMethod(), parameter), parameter).Compile();
            Getter = Expression.Lambda<Func<T>>(Expression.Call(instanceExpression, propertyInfo.GetGetMethod())).Compile();
        }
        else if (memberExpression.Member is FieldInfo fieldInfo)
        {
            Setter = Expression.Lambda<Action<T>>(Expression.Assign(memberExpression, parameter), parameter).Compile();
            Getter = Expression.Lambda<Func<T>>(Expression.Field(instanceExpression,fieldInfo)).Compile();
        }

    }

    public void Set(T value) => Setter(value);

    public T Get() => Getter();
}
Run Code Online (Sandbox Code Playgroud)

并使用它像:

var accessor = new Accessor<string>(() => myClient.WorkPhone);
accessor.Set("12345");
Assert.Equal(accessor.Get(), "12345");
Run Code Online (Sandbox Code Playgroud)

  • 最佳答案在这里。你知道什么是性能影响吗?将它包含在答案中会很好。我对表达式树不太熟悉,但我希望,使用 Compile() 意味着访问器实例实际上包含 IL 编译代码,因此使用固定数量的访问器 n 次是可以的,但使用总共 n 个访问器(高 ctor 成本)不会。 (3认同)
  • @EricOuellet _“它应该对性能产生巨大的影响”_。基于什么?假设每次都不会重新创建 Accessor&lt;T&gt; 类,我希望对 Get() 和 Set() 的调用对性能的影响最小。当然,正确的答案是测量并找出来。 (2认同)

Pel*_*let 7

如果你想同时获取和设置属性,你可以在 C#7 中使用它:

GetString(
    inputString,
    (() => client.WorkPhone, x => client.WorkPhone = x))

void GetString(string inValue, (Func<string> get, Action<string> set) outValue)
{
    if (!string.IsNullOrEmpty(outValue))
    {
        outValue.set(inValue);
    }
}
Run Code Online (Sandbox Code Playgroud)