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

C# 在C中通过引用传递属性#,c#,properties,pass-by-reference,C#,Properties,Pass By Reference,我正试图做到以下几点: GetString( inputString, ref Client.WorkPhone) private void GetString(string inValue, ref string outValue) { if (!string.IsNullOrEmpty(inValue)) { outValue = inValue; } } 这给了我一个编译错误。我想我想要达到的目标非常明确。基本上,我希望GetSt

我正试图做到以下几点:

GetString(
    inputString,
    ref Client.WorkPhone)

private void GetString(string inValue, ref string outValue)
{
    if (!string.IsNullOrEmpty(inValue))
    {
        outValue = inValue;
    }
}
这给了我一个编译错误。我想我想要达到的目标非常明确。基本上,我希望
GetString
将输入字符串的内容复制到
Client
WorkPhone
属性


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

这是不可能的。你可以说

Client.WorkPhone = GetString(inputString, Client.WorkPhone);
其中,
WorkPhone
是一个可写的
string
属性,
GetString
的定义更改为

private string GetString(string input, string current) { 
    if (!string.IsNullOrEmpty(input)) {
        return input;
    }
    return current;
}
这将具有您似乎正在尝试的相同语义


这是不可能的,因为属性实际上是一对伪装的方法。每个属性都提供了可通过类似字段的语法访问的getter和setter。当您尝试按照建议调用
GetString
时,您传递的是一个值,而不是一个变量。您传入的值是从getter
get\u WorkPhone

返回的值,这在C语言规范的第7.4.1节中有介绍。在参数列表中,只有变量引用可以作为ref或out参数传入。属性不符合变量引用的条件,因此无法使用

属性不能通过引用传递。以下是一些可以绕过此限制的方法

1.返回值 2.代表
您可以尝试创建一个对象来保存属性值。这样,您可以传递对象,并且仍然可以访问其中的属性。

而无需复制属性

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);
    }
}
void Main()
{
var client=new client();
NullSafeSet(“测试”,s=>client.Name=s);
Assert(person.Name==“test”);
NullSafeSet(“,s=>client.Name=s);
Assert(person.Name==“test”);
NullSafeSet(null,s=>client.Name=s);
Assert(person.Name==“test”);
}
void NullSafeSet(字符串值、动作设置器)
{
如果(!string.IsNullOrEmpty(值))
{
设定器(值);
}
}

另一个尚未提及的技巧是让实现属性的类(例如
Foo
类型
Bar
)也定义委托
委托无效ActByRef(参考T1 p1,参考T2 p2)并实现一个方法
Actonfo(ref Bar it,ActByRef proc,ref TX1 extraParam1)
(可能还有两个和三个“额外参数”的版本),该方法将
Foo
的内部表示形式作为
ref
参数传递给提供的过程。与处理该物业的其他方法相比,这有两大优势:

  • 财产更新为“就地”;如果属性的类型与“Interlocked”方法兼容,或者如果它是具有此类类型的公开字段的结构,“Interlocked”方法可用于对属性执行原子更新。
  • 如果属性是公开的字段结构,则可以修改该结构的字段,而无需对其进行任何冗余复制。
  • 如果'ActByRef'方法将一个或多个'ref'参数从其调用方传递给提供的委托,则可以使用单例委托或静态委托,从而避免在运行时创建闭包或委托。
  • 财产知道何时“使用”。虽然在持有锁的同时执行外部代码时始终需要谨慎,但如果可以信任调用方不要在其回调中执行任何可能需要另一个锁的操作,那么让该方法使用锁来保护属性访问可能是可行的,这样,与“CompareeExchange”不兼容的更新仍然可以准原子方式执行。 传递东西be
    ref
    是一个很好的模式;可惜它没有被更多地使用。

    只需稍微扩展一下即可。使用多泛型参数,使属性不限于字符串

    void GetString<TClass, TProperty>(string input, TClass outObj, Expression<Func<TClass, TProperty>> outExpr)
    {
        if (!string.IsNullOrEmpty(input))
        {
            var expr = (MemberExpression) outExpr.Body;
            var prop = (PropertyInfo) expr.Member;
            if (!prop.GetValue(outObj).Equals(input))
            {
                prop.SetValue(outObj, input, null);
            }
        }
    }
    
    void GetString(字符串输入、TClass outObj、表达式outExpr)
    {
    如果(!string.IsNullOrEmpty(输入))
    {
    var expr=(MemberExpression)outExpr.Body;
    var prop=(PropertyInfo)expr.Member;
    如果(!prop.GetValue(outObj).Equals(输入))
    {
    属性设置值(outObj,输入,null);
    }
    }
    }
    
    我使用ExpressionTree变量和c#7编写了一个包装器(如果有人感兴趣):

    公共类访问器
    {
    私人行动制定者;
    私有函数Getter;
    公共访问器(表达式表达式表达式)
    {
    var memberExpression=(memberExpression)expr.Body;
    var instanceExpression=memberExpression.Expression;
    var参数=表达式参数(typeof(T));
    if(memberExpression.Member是PropertyInfo PropertyInfo)
    {
    Setter=Expression.Lambda(Expression.Call(instanceExpression,propertyInfo.GetSetMethod(),parameter),parameter.Compile();
    Getter=Expression.Lambda(Expression.Call(instanceExpression,propertyInfo.GetGetMethod()).Compile();
    }
    else if(memberExpression.Member为FieldInfo FieldInfo)
    {
    Setter=Expression.Lambda(Expression.Assign(memberExpression,parameter),parameter.Compile();
    Getter=Expression.Lambda(Expression.Field(instanceExpression,fieldInfo)).Compile();
    }
    }
    公共无效集(T值)=>设置器(值);
    public T Get()=>Getter();
    }
    
    然后像这样使用它:

    var accessor = new Accessor<string>(() => myClient.WorkPhone);
    accessor.Set("12345");
    Assert.Equal(accessor.Get(), "12345");
    
    var访问器=新访问器(()=>myClient.WorkPhone);
    存取器集(“12345”);
    Assert.Equal(accessor.Get(),“12345”);
    
    您不能
    ref
    属性,但是如果您的函数同时需要
    get
    set
    访问,您可以传递定义了属性的类的实例:

    public class Property<T>
    {
        public delegate T Get();
        public delegate void Set(T value);
        private Get get;
        private Set set;
        public T Value {
            get {
                return get();
            }
            set {
                set(value);
            }
        }
        public Property(Get get, Set set) {
            this.get = get;
            this.set = set;
        }
    }
    
    公共类属性
    {
    公共委托无法获取();
    公共委托无效集(T值);
    私人获得;
    私有集;
    公共价值{
    得到{
    返回get();
    }
    设置{
    设置(值);
    }
    }
    公共财产(
    
    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);
        }
    }
    
    void GetString<TClass, TProperty>(string input, TClass outObj, Expression<Func<TClass, TProperty>> outExpr)
    {
        if (!string.IsNullOrEmpty(input))
        {
            var expr = (MemberExpression) outExpr.Body;
            var prop = (PropertyInfo) expr.Member;
            if (!prop.GetValue(outObj).Equals(input))
            {
                prop.SetValue(outObj, input, null);
            }
        }
    }
    
    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();
    }
    
    var accessor = new Accessor<string>(() => myClient.WorkPhone);
    accessor.Set("12345");
    Assert.Equal(accessor.Get(), "12345");
    
    public class Property<T>
    {
        public delegate T Get();
        public delegate void Set(T value);
        private Get get;
        private Set set;
        public T Value {
            get {
                return get();
            }
            set {
                set(value);
            }
        }
        public Property(Get get, Set set) {
            this.get = get;
            this.set = set;
        }
    }
    
    class Client
    {
        private string workPhone; // this could still be a public property if desired
        public readonly Property<string> WorkPhone; // this could be created outside Client if using a regular public property
        public int AreaCode { get; set; }
        public Client() {
            WorkPhone = new Property<string>(
                delegate () { return workPhone; },
                delegate (string value) { workPhone = value; });
        }
    }
    class Usage
    {
        public void PrependAreaCode(Property<string> phone, int areaCode) {
            phone.Value = areaCode.ToString() + "-" + phone.Value;
        }
        public void PrepareClientInfo(Client client) {
            PrependAreaCode(client.WorkPhone, client.AreaCode);
        }
    }
    
    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);
        }
    }
    
    public class MyClass
    {
        public class MyStuff
        {
            string foo { get; set; }
        }
    
        private ObservableCollection<MyStuff> _collection;
    
        public ObservableCollection<MyStuff> Items { get { return _collection; } }
    
        public MyClass()
        {
            _collection = new ObservableCollection<MyStuff>();
            this.LoadMyCollectionByRef<MyStuff>(ref _collection);
        }
    
        public void LoadMyCollectionByRef<T>(ref ObservableCollection<T> objects_collection)
        {
            // Load refered collection
        }
    }
    
    var phone = Client.WorkPhone;
    GetString(input, ref phone);
    Client.WorkPhone = phone;