簡體   English   中英

關於“成員”的C#擴展方法

[英]C# Extension methods on “members”

我有一些擴展方法,可以像這樣使用:

MyType myObject; 
string displayName = myObject.GetDisplayName(x => x.Property);

這里的問題是它需要一個實例,即使擴展方法只需要MyType類型。 因此,如果沒有實例,則需要像這樣調用:

string displayName = BlahBlahUtility.GetDisplayName((MyTpe x) => x.Property);

哪個不再那么好了。

有沒有辦法為這種情況編寫更好的語法?

我真正想做的是這(偽語言):

string displayName = MyType.Property.GetDisplayName()

這當然不適用於C#。

但是這樣的事情呢:

string displayName = ((MyType x) => x.Property).GetDisplayName();

這也是不可能的(在lambda之后,不接受點)。

有任何想法嗎?


編輯

我的“最喜歡的語法” MyType.Property.GetDisplayName()似乎具有誤導性。 我這里不討論靜態屬性。 我知道這種語法是不可能的。 我只是試圖用偽語言表示,需要哪些信息。 這將是理想的,每一個額外的東西只是語法開銷。 任何與此接近的工作語法都會很棒。

我不想寫一定的擴展方法。 我想要一個簡單,可讀和編譯時安全的語法, 使用任何語言功能

查看Lokad共享庫中的ExpressReflect類。 認為他們可能會幫助您嘗試做什么。 在這里閱讀更多:

從您的評論:“我想要一個簡單的編譯時安全語法來獲取有關成員的信息”。

這是一個非常頻繁要求的功能,已經在C#團隊的會議中討論了大約十年,但從來沒有被優先考慮過高,不能被包括在內。

這篇博客文章解釋了原因:

http://blogs.msdn.com/ericlippert/archive/2009/05/21/in-foof-we-trust-a-dialogue.aspx

所以現在,你只是要與一個缺失的功能作斗爭。 也許您可以發布有關更廣泛問題的更多信息,看看人們是否可以提出不同的方法。

更新

沒有關於你的問題的更多信息,這只是猜測。 但是如果你有一個代表一個值但又帶有額外“元”信息的屬性,你總是可以將它表示為一個新類型並使用“注入”步驟來設置所有內容。

這是一個建議的這種“元屬性”的抽象接口:

public interface IMetaProperty<TValue>
{
    TValue Value { get; set; }

    string DisplayName { get; }

    event Action<TValue, TValue> ValueChanged;
}

屬性的值只是另一個子屬性,其類型由用戶定義。

我已經輸入了顯示名稱,並且作為獎勵,你有一個事件在值發生變化時觸發(因此你可以免費獲得“可觀察性”)。

要在類中擁有這樣的屬性,您可以這樣聲明:

public class SomeClass
{
    public IMetaProperty<string> FirstName { get; private set; }
    public IMetaProperty<string> LastName { get; private set; }
    public IMetaProperty<int> Age { get; private set; }

    public SomeClass() { MetaProperty.Inject(this); }
}

請注意屬性上的setter是如何私有的。 這會阻止任何人意外設置屬性本身而不是設置Value子屬性。

所以這意味着類必須設置這些屬性,因此它們不僅僅是null 它通過調用一個神奇的Inject方法來實現,它可以在任何類上工作:

public static class MetaProperty
{
    // Make it convenient for us to fill in the meta information
    private interface IMetaPropertyInit
    {
        string DisplayName { get; set; }
    }

    // Implementation of a meta-property
    private class MetaPropertyImpl<TValue> : IMetaProperty<TValue>, 
                                             IMetaPropertyInit
    {
        private TValue _value;

        public TValue Value
        {
            get { return _value; }
            set
            {
                var old = _value;
                _value = value;
                ValueChanged(old, _value);
            }
        }

        public string DisplayName { get; set; }

        public event Action<TValue, TValue> ValueChanged = delegate { };
    }

    public static void Inject(object target)
    {
        // for each meta property...
        foreach (var property in target.GetType().GetProperties()
            .Where(p => p.PropertyType.IsGenericType && 
                        p.PropertyType.GetGenericTypeDefinition() 
                            == typeof(IMetaProperty<>)))
        {
            // construct an implementation with the correct type
            var impl = (IMetaPropertyInit) 
                typeof (MetaPropertyImpl<>).MakeGenericType(
                    property.PropertyType.GetGenericArguments()
                ).GetConstructor(Type.EmptyTypes).Invoke(null);

            // initialize any meta info (could examine attributes...)
            impl.DisplayName = property.Name;

            // set the value
            property.SetValue(target, impl, null);
        }
    }
}

它只是使用反射來查找隱藏在對象中的所有IMetaProperty插槽,並使用實現填充它們。

所以現在SomeClass的用戶可以說:

var sc = new SomeClass
             {
                 FirstName = { Value = "Homer" },
                 LastName = { Value = "Simpson" },
                 Age = { Value = 38 },
             };

Console.WriteLine(sc.FirstName.DisplayName + " = " + sc.FirstName.Value);

sc.Age.ValueChanged += (from, to) => 
    Console.WriteLine("Age changed from " + from + " to " + to);

sc.Age.Value = 39;

// sc.Age = null; compiler would stop this

如果您已經在使用IOC容器,則可以實現其中一些而無需直接進行反射。

看起來您正在嘗試創建靜態擴展方法?

DateTime yesterday = DateTime.Yesterday(); // Static extension.

代替

DateTime yesterday = DateTime.Now.Yesterday(); // Extension on DateTime instance.

如果這是你想要實現的目標,我認為在當前版本的C#中它是不可能的。

聽起來你將圖層集成得太緊了。 通常在這種情況下,我會讓表示層決定GetDisplayName()的實現,而不是使它成為屬性本身的擴展。 您可以創建一個名為MyTypeDisplayer的界面或任何您想要的界面,讓它有多個實現,而不是限制您使用單個顯示實現。

這里的問題是,無法通過實例MyType獲取對非靜態方法的引用。[Member]。 這些只能通過對類型實例的引用來看出。 您也無法在類型聲明的頂部構建擴展方法,僅在類型的實例上構建 - 即擴展方法本身必須使用類型的實例(此T x)來定義。

然而,可以像這樣定義表達式來獲取對靜態成員的引用:((MyType x)=> MyType.Property)

可以做類似於字符串displayName =((MyType x)=> x.Property).GetDisplayName(); 第一個問題是保證編譯器將您的(x => x.Property)視為表達式而不是action / func等...要做到這一點,可能需要這樣做:

string displayName = ((Expression<Func<PropertyType>>)((MyType x) => x.Property).GetDisplayName();

然后必須像這樣定義擴展方法:

public static string GetDisplayName<T>(this Expression<Func<T>> expression)

如果您的成員也是方法,您可能還必須在Expression<Action>> and Expression<Action<T>>之上定義擴展方法。

你可以在表達式之后做一個點 - 這是編譯方法所在的位置。

附:

我認為在沒有類型1實例的情況下對擴展方法的靜態調用需要做“反射”以確定成員名稱仍然是最干凈的語法 - 這樣你仍然可以使用擴展當一個實例沒有實例時,使用類型實例並回退到靜態調用定義=> MyExtensionClass.GetDisplayName(TypeOfX x => TypeOfX.StaticMember OR x.Property/Member)時的方法

如果您對屬性進行界面,則可以在界面上進行擴展:

namespace Linq1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyType o = new MyType();
            o.Property.GetDisplayName();
        }
    }

    public class MyType
    {
        public IDisplayableProperty Property { get; set; }
    }

    public interface IDisplayableProperty 
    {
        string GetText();
    }

    public class MyProperty1 : IDisplayableProperty 
    {
        public string GetText() { return "MyProperty2"; }
    }

    public class MyProperty2 : IDisplayableProperty 
    {
        public string GetText() { return "MyProperty2"; }
    }

    public static class Extensions
    {
        public static string GetDisplayName(this IDisplayableProperty o)
        {
            return o.GetText();
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM