C # 'da referans ile özellikleri iletme


224

Aşağıdakileri yapmaya çalışıyorum:

GetString(
    inputString,
    ref Client.WorkPhone)

private void GetString(string inValue, ref string outValue)
{
    if (!string.IsNullOrEmpty(inValue))
    {
        outValue = inValue;
    }
}

Bu bana derleme hatası veriyor. Sanırım neyi başarmaya çalıştığım oldukça açık. Temelde istediğim GetStringiçin bir giriş dizesi içeriğini kopyalamak için WorkPhonemülkiyet Client.

Bir mülkü referans ile geçirmek mümkün müdür?


Yanıtlar:


424

Özellikler başvuru ile iletilemez. Bu sınırlamayı aşmanın birkaç yolu aşağıda verilmiştir.

1. Dönüş Değeri

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");
}

2. Delege

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");
}

3. LINQ İfadesi

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");
}

4. Yansıma

void GetString(string input, object target, string propertyName)
{
    if (!string.IsNullOrEmpty(input))
    {
        var 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");
}

2
Örnekleri seviyorum. Bunun da uzantı yöntemleri için harika bir yer olduğunu düşünüyorum: codegenel statik dize GetValueOrDefault (bu dize s, dize isNullString) {if (s == null) {s = isNullString; } İadeler; } void Main () {person.MobilePhone.GetValueOrDefault (person.WorkPhone); }
BlackjacketMack

9
Çözüm 2'de, 2. parametre getOutputgerekli değildir.
Jaider

32
Ve çözüm 3 için daha iyi bir isim Düşünme olduğunu düşünüyorum.
Jaider

1
Çözüm 2, getOutput 2. parametre gereksiz - doğru ama ben ayarlı olduğunu değeri görmek için GetString içinde kullandım. Bu parametre olmadan bunu nasıl yapacağınızdan emin değilim.
Petras

3
@GoneCodingGoodbye: ama en az verimli yaklaşım. Bir mülke basitçe bir değer atamak için yansıma kullanmak, bir somun kırmak için balyoz almak gibidir. Ayrıca, bir GetStringözellik ayarlaması gereken bir yöntem açıkça adlandırılmıştır.
Tim Schmelter

27

özelliği çoğaltmadan

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);
    }
}

4
Adını değiştirmek için 1 GetStringiçin NullSafeSeteski burada hiçbir mantıklı nedeni.
Camilo Martin

25

ExpressionTree varyantını ve c # 7'yi (birileri ilgiliyse) kullanarak bir sarıcı yazdım:

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();
}

Ve şöyle kullanın:

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

3
En iyi cevap burada. Performansın etkisinin ne olduğunu biliyor musunuz? Cevap içinde yer alması iyi olurdu. Ifade ağaçları ile çok aşina değilim ama Compile () kullanarak erişimci örneği aslında derlenmiş kod içerdiği anlamına gelir ve bu nedenle sabit sayıda erişimci n-kez kullanarak tamam, ama toplam n erişimcileri ( yüksek ctor maliyeti) olmaz.
mancze

Harika kod! Bence en iyi cevap bu. En genel olanı. Dediği gibi mancze ... Performans üzerinde büyük bir etkisi olmalı ve sadece kod netliğinin performanstan daha önemli olduğu bir bağlamda kullanılmalıdır.
Eric Ouellet

5

Her iki özelliği de almak ve ayarlamak istiyorsanız, bunu C # 7'de kullanabilirsiniz:

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);
    }
}

3

Henüz bahsedilmeyen bir başka numara, bir özelliği uygulayan sınıfa (örneğin Footür Bar) aynı zamanda bir delege tanımlamak ve iç temsilini geçirecek delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);bir yöntem ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1)(ve muhtemelen iki ve üç "ekstra parametre" için sürümler) Foouygulamaktır. refparametre olarak verilen prosedür . Bu, mülkle çalışmanın diğer yöntemlerine göre birkaç büyük avantaja sahiptir:

  1. Özellik "yerinde" güncellenir; özellik `` Kilitli '' yöntemlerle uyumlu bir türdeyse veya bu türdeki açık alanlarla bir yapı ise, `` Kilitli '' yöntemler, mülkün atomik güncellemelerini gerçekleştirmek için kullanılabilir.
  2. Özellik bir açık alan yapısı ise, yapının alanları, herhangi bir yedek kopyasını yapmak zorunda kalmadan değiştirilebilir.
  3. `ActByRef` yöntemi bir veya daha fazla` ref` parametresini çağrısından verilen delege'ye geçirirse, tekli veya statik bir delege kullanmak, böylece çalışma zamanında kapatma veya delege oluşturma ihtiyacını ortadan kaldırmak mümkün olabilir.
  4. Mülkiyet ne zaman "ile çalışılıyor" bilir. Bir kilidi tutarken harici kod yürütme konusunda dikkatli olmak her zaman gerekli olsa da, arayanların geri aramalarında başka bir kilit gerektirebilecek herhangi bir şey yapmamalarına güvenebilirse, yöntemin özellik erişimini bir kilidi, öyle ki güncellemeler hala Atomik olarak yapılabilir.

Bir şeyleri geçirmek refmükemmel bir kalıptur; çok kötü, daha fazla kullanılmıyor.


3

Nathan'ın Linq Expression çözümüne biraz genişleme . Özelliğin dize ile sınırlı olmaması için çoklu genel parametre kullanın.

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);
        }
    }
}

2

Bu, C # dil spesifikasyonunun 7.4.1 bölümünde ele alınmıştır. Bağımsız değişken listesinde yalnızca bir değişken başvurusu ref veya out parametresi olarak geçirilebilir. Bir özellik, değişken başvurusu olarak nitelendirilmez ve dolayısıyla kullanılamaz.


2

Bu mümkün değil. Söyleyebilirdin

Client.WorkPhone = GetString(inputString, Client.WorkPhone);

WorkPhoneYazılabilir bir stringözellik nerede ve tanımı GetStringolarak değiştirildi

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

Bu, denediğiniz gibi aynı semantiğe sahip olacaktır.

Bu mümkün değildir çünkü bir özellik gerçekten kılık değiştirmiş bir çift yöntemdir. Her özellik, alana benzer sözdizimi ile erişilebilen alıcıları ve ayarlayıcıları etkinleştirir. Önerdiğiniz GetStringgibi aramaya çalıştığınızda , ilettiğiniz şey bir değişken değil, bir değerdir. Geçirdiğiniz değer, alıcıdan döndürülen değerdir get_WorkPhone.


1

Yapmaya çalışabileceğiniz özellik değerini tutmak için bir nesne oluşturmaktır. Bu şekilde nesneyi geçirebilir ve içindeki özelliğe hala erişebilirsiniz.


1

Özellikler başvuru ile iletilemiyor mu? Daha sonra bir alan yapın ve özelliği herkese açık olarak başvurmak için kullanın:

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
    }
}

0

refBir özelliği kullanamazsınız , ancak işlevlerinizin her ikisine de getve seterişime ihtiyacı varsa, tanımlanan bir özelliğe sahip bir sınıf örneğini aktarabilirsiniz:

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;
    }
}

Misal:

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);
    }
}

0

Bu işlev kodunuzdaysa ve onu değiştirebilirsiniz. Kabul edilen yanıt iyidir. Ancak bazen bazı harici kitaplıklardan bir nesne ve bir işlev kullanmak zorunda kalırsınız ve özellik ve işlev tanımını değiştiremezsiniz. Sonra sadece geçici bir değişken kullanabilirsiniz.

var phone = Client.WorkPhone;
GetString(input, ref phone);
Client.WorkPhone = phone;

0

Bu konuya oy vermek için, bunun dile nasıl eklenebileceğine dair aktif bir öneri. Bunun (en iyi) bunu yapmanın en iyi yolu olduğunu söylemiyorum, kendi önerinizi dile getirmekten çekinmeyin. Ancak özelliklerin Visual Basic gibi zaten ref tarafından geçirilmesine izin vermek, büyük ölçüde bazı kodları basitleştirmeye yardımcı olur ve oldukça sık!

https://github.com/dotnet/csharplang/issues/1235

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.