简体   繁体   English

关于“成员”的C#扩展方法

[英]C# Extension methods on “members”

I have some extension methods which could be used like this: 我有一些扩展方法,可以像这样使用:

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

The problem here is that it needs an instance, even if the extension method only needs the type MyType . 这里的问题是它需要一个实例,即使扩展方法只需要MyType类型。 So if there is no instance, it needs to be called like this: 因此,如果没有实例,则需要像这样调用:

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

Which is not so nice anymore. 哪个不再那么好了。

Is there a way to write better syntax for such cases? 有没有办法为这种情况编写更好的语法?

What I actually want to do is this (pseudo language): 我真正想做的是这(伪语言):

string displayName = MyType.Property.GetDisplayName()

Which of course does not work with C#. 这当然不适用于C#。

But what about something like this: 但是这样的事情呢:

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

This is also not possible (after a lambda, a dot is not accepted). 这也是不可能的(在lambda之后,不接受点)。

Any ideas? 有任何想法吗?


Edit : 编辑

My "favorite syntax" MyType.Property.GetDisplayName() seems to be misleading. 我的“最喜欢的语法” MyType.Property.GetDisplayName()似乎具有误导性。 I don't talk about static properties here. 我这里不讨论静态属性。 I know that this syntax won't be possible. 我知道这种语法是不可能的。 I just tried to show in pseudo language, what information is necessary. 我只是试图用伪语言表示,需要哪些信息。 This would be ideal, every additional stuff is just syntactical overhead. 这将是理想的,每一个额外的东西只是语法开销。 Any working syntax that is close to this would be great. 任何与此接近的工作语法都会很棒。

I don't want to write a certain extension method. 我不想写一定的扩展方法。 I want an easy, readable and compile time safe syntax, using any language feature . 我想要一个简单,可读和编译时安全的语法, 使用任何语言功能

Have a look at the Express and Reflect classes in the Lokad Shared Libraries . 查看Lokad共享库中的ExpressReflect类。 Think they may help out with what you are trying to do. 认为他们可能会帮助您尝试做什么。 Read more here: 在这里阅读更多:

From your comment: "I want an easy and compile time safe syntax to get information about members". 从您的评论:“我想要一个简单的编译时安全语法来获取有关成员的信息”。

This is a very frequently requested feature and has been discussed in the C# team's meetings for about a decade, but has never been prioritised high enough to be included. 这是一个非常频繁要求的功能,已经在C#团队的会议中讨论了大约十年,但从来没有被优先考虑过高,不能被包括在内。

This blog post explains why: 这篇博客文章解释了原因:

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

So for now, you're just going to be fighting against a missing feature. 所以现在,你只是要与一个缺失的功能作斗争。 Maybe you could post more information about your broader problem and see if people can suggest different approaches. 也许您可以发布有关更广泛问题的更多信息,看看人们是否可以提出不同的方法。

Update 更新

Without more info about your problem this is just guesswork. 没有关于你的问题的更多信息,这只是猜测。 But if you have a property that represents a value but also carries additional "meta" information, you could always represent that as a new type and use an "injection" step to set everything up. 但是如果你有一个代表一个值但又带有额外“元”信息的属性,你总是可以将它表示为一个新类型并使用“注入”步骤来设置所有内容。

Here's a suggested abstract interface to such a "meta property": 这是一个建议的这种“元属性”的抽象接口:

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

    string DisplayName { get; }

    event Action<TValue, TValue> ValueChanged;
}

The value of the property is just another sub-property, with its type defined by the user. 属性的值只是另一个子属性,其类型由用户定义。

I've put in the display name, and also as a bonus you've got an event that fires when the value changes (so you get "observability" for free). 我已经输入了显示名称,并且作为奖励,你有一个事件在值发生变化时触发(因此你可以免费获得“可观察性”)。

To have properties like this in a class, you'd declare it like this: 要在类中拥有这样的属性,您可以这样声明:

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

Note how the setters on the properties are private. 请注意属性上的setter是如何私有的。 This stops anyone from accidentally setting the property itself instead of setting the Value sub-property. 这会阻止任何人意外设置属性本身而不是设置Value子属性。

So this means the class has to set up those properties so they aren't just null . 所以这意味着类必须设置这些属性,因此它们不仅仅是null It does this by calling a magic Inject method, which can work on any class: 它通过调用一个神奇的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);
        }
    }
}

It just uses reflection to find all the IMetaProperty slots hiding in the object, and fills them in with an implementation. 它只是使用反射来查找隐藏在对象中的所有IMetaProperty插槽,并使用实现填充它们。

So now a user of SomeClass could say: 所以现在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

If you're already using an IOC container you may be able to achieve some of this without going directly to reflection. 如果您已经在使用IOC容器,则可以实现其中一些而无需直接进行反射。

It looks like you're trying to create a static extension method? 看起来您正在尝试创建静态扩展方法?

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

Instead of 代替

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

If this is what you're trying to pull off, I do not believe it is possible in the current version of C#. 如果这是你想要实现的目标,我认为在当前版本的C#中它是不可能的。

It sounds like you are integrating layers a little too tightly. 听起来你将图层集成得太紧了。 Normally in this type of situation I would let the presentation layer decide the implementation of GetDisplayName() instead of making it an extension of the property itself. 通常在这种情况下,我会让表示层决定GetDisplayName()的实现,而不是使它成为属性本身的扩展。 You could create an interface called MyTypeDisplayer or whatever you fancy, and let there be multiple implementations of it not limiting you to a single display implementation. 您可以创建一个名为MyTypeDisplayer的界面或任何您想要的界面,让它有多个实现,而不是限制您使用单个显示实现。

The issue here is that one cannot get a reference to non-static methods via instance MyType.[Member]. 这里的问题是,无法通过实例MyType获取对非静态方法的引用。[Member]。 These can only be seen through a reference to an instance of the type. 这些只能通过对类型实例的引用来看出。 You also cannot build an extension method on-top of a type declaration, only on an instance of a type - that is the extension method itself has to be defined using an instance of a type (this T x). 您也无法在类型声明的顶部构建扩展方法,仅在类型的实例上构建 - 即扩展方法本身必须使用类型的实例(此T x)来定义。

One can however define the expression like this to get a reference to static members: ((MyType x) => MyType.Property) 然而,可以像这样定义表达式来获取对静态成员的引用:((MyType x)=> MyType.Property)

One could do something similar to string displayName = ((MyType x) => x.Property).GetDisplayName(); 可以做类似于字符串displayName =((MyType x)=> x.Property).GetDisplayName(); The first issue is guaranteeing that the compiler treats your (x=> x.Property) as an Expression rather than an action/func etc... To do this one might need to do this: 第一个问题是保证编译器将您的(x => x.Property)视为表达式而不是action / func等...要做到这一点,可能需要这样做:

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

The extension method would then have to be defined like this: 然后必须像这样定义扩展方法:

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

You might also have to define an extension method on top of Expression<Action>> and Expression<Action<T>> if your members are also methods. 如果您的成员也是方法,您可能还必须在Expression<Action>> and Expression<Action<T>>之上定义扩展方法。

You can do a dot after an Expression - this is where the Compile method would reside. 你可以在表达式之后做一个点 - 这是编译方法所在的位置。

Appended: 附:

I think the static call to the extension method in cases that one doesn't have an instance of the type one needs to do "reflection" on to determine a Members name would be the cleanest syntax still - this way you could still use the extension method when using an instance of a type and fall back to the static call definition => MyExtensionClass.GetDisplayName(TypeOfX x => TypeOfX.StaticMember OR x.Property/Member) when one doesn't have an instance 我认为在没有类型1实例的情况下对扩展方法的静态调用需要做“反射”以确定成员名称仍然是最干净的语法 - 这样你仍然可以使用扩展当一个实例没有实例时,使用类型实例并回退到静态调用定义=> MyExtensionClass.GetDisplayName(TypeOfX x => TypeOfX.StaticMember OR x.Property/Member)时的方法

If you interface your properties, you could make the extension on the interface instead: 如果您对属性进行界面,则可以在界面上进行扩展:

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