反射。我们可以用它实现什么?

13

我正在阅读和学习有关C#反射的内容。很想知道它如何帮助我在日常工作中使用,因此我想听听比我更有经验的人分享使用反射可以实现哪些样例或者想法,以及如何减少我们编写的代码量。

谢谢。


1
相关问题:https://dev59.com/jXI-5IYBdhLWcg3wbXtN请在3分钟内解释反射是什么。 - Brian Rasmussen
1
相关的,但我个人认为那个问题并不是很有帮助。积分是反求导,但我敢打赌告诉你这个并没有教给你太多东西,即使你知道什么是导数。:D - Gordon Gustafson
14个回答

12

我最近使用它向枚举字段添加自定义属性:

public enum ShapeName
{
    // Lines
    [ShapeDescription(ShapeType.Line, "Horizontal Scroll Distance", "The horizontal distance to scroll the browser in order to center the game.")]
    HorizontalScrollBar,
    [ShapeDescription(ShapeType.Line, "Vertical Scroll Distance", "The vertical distance to scroll the browser in order to center the game.")]
    VerticalScrollBar,
}

使用反射获取字段:

    public static ShapeDescriptionAttribute GetShapeDescription(this ShapeName shapeName)
    {
        Type type = shapeName.GetType();
        FieldInfo fieldInfo = type.GetField(shapeName.ToString());
        ShapeDescriptionAttribute[] attribs = fieldInfo.GetCustomAttributes(typeof(ShapeDescriptionAttribute), false) as ShapeDescriptionAttribute[];

        return (attribs != null && attribs.Length > 0) ? attribs[0] : new ShapeDescriptionAttribute(ShapeType.NotSet, shapeName.ToString());
    }

class属性:

[AttributeUsage(AttributeTargets.Field)]
public class ShapeDescriptionAttribute: Attribute
{
    #region Constructor
    public ShapeDescriptionAttribute(ShapeType shapeType, string name) : this(shapeType, name, name) { }

    public ShapeDescriptionAttribute(ShapeType shapeType, string name, string description)
    {
        Description = description;
        Name = name;
        Type = shapeType;
    }
    #endregion

    #region Public Properties
    public string Description { get; protected set; }

    public string Name { get; protected set; }

    public ShapeType Type { get; protected set; }
    #endregion
}

10

一般来说,反射允许您访问关于对象的元数据。结合其他技术使用反射可以使您的程序更加动态化。例如,您可以加载DLL并确定它是否包含接口的实现。您可以使用此功能在运行时发现支持功能的dll。您可以使用此功能扩展应用程序而无需重新编译和重启。

Visual Studio中的Intellisense使用反射为您提供有关正在使用的对象的信息。

请注意,使用反射会带来成本。反射对象可能会很慢。但是如果您需要它,反射是一个非常有用的工具。


1
您可以通过各种方式来降低这些成本;基于反射的代码可以和常规代码一样快。 - Marc Gravell

3

其中一个用途是:您可以创建一个插件架构,在配置文件中指定要使用的类的名称。使用反射,您可以将此字符串获取并创建所请求对象的实例。如果该对象实现了已知接口,则可以通过普通(非反射)代码使用它。


3

对于不需要了解调用者的库代码(如泛型),但需要更丰富地访问数据的情况下,这是非常宝贵的。例如:

  • ORM(材料化等)
  • 序列化/反序列化
  • 对象克隆/深拷贝
  • UI/绑定代码(严格来说这是ComponentModel,但您可以混合使用两种方法-例如HyperDescriptor)

当然,您应该尽量减少反射操作的次数,但您可以通过缓存从Delegate.CreateDelegate / Expression / DynamicMethod创建的委托来减轻成本。


1

VS中的属性窗口是基于反射的 - 如果您创建了一个用户控件,可以立即从PropertyGrid(如果需要,也可以使用它)修改其上的任何属性。当然,您可以添加属性以增强其显示方式(这些属性通过反射访问)。

我还使用它来实现自定义二进制序列化类。

在这里,我有一个类,在其中使用反射对其进行序列化/反序列化 - 并提供用于附加UI信息的属性。

[TypeConverter(typeof(IndexedExpandableObjectConverter))]
[BinarySerializeable]
public sealed class Socket
{
    #region Fields (7) 

    [SerializedPosition(0)]
    Byte _mode = 1;

    ...

    [SerializedPositionAttribute(4)]
    UInt16 _localPort;

    ...

#区域 属性 (5)

    [DisplayName("Listning Port")]
    [Description("The port which the socket will listen for connections on")]
    [DisplayIndex (0)]
    public UInt16 LocalPort
    {
        get { return _localPort; }
        set { _localPort = value; }
    }

    ...

而序列化函数-正如您所看到的,它只需要一个对象和您想要的字节顺序(大小端)。其他所有内容都由反射决定。默认的SerializationProvider使用对象内部字段(私有或公共)上的SerializedPosition属性来工作。

public static Byte[] Serialize(Object obj, ByteOrder streamOrder)
{

    var provider = GetProvider(obj);

    if (provider.CanSerialize(obj.GetType()))
        return provider.Serialize(obj, streamOrder);

    throw new ArgumentException(obj.GetType() + " is non-serialisable by the specified provider '" + provider.GetType().FullName + "'.");
}


private static IBinarySerializatoinProvider GetProvider(Object obj)
{

    var providerAttrib = Reflector.GetAttribute<BinarySerializationProviderAttribute>(obj);

    if (providerAttrib != null)
        return CreateProvider(providerAttrib.ProviderType);

    return CreateProvider(typeof(SerializationProvider));
}

1

这是一种基于枚举或魔术字符串执行方法的方式...


    public enum ReflectionTestMethods
    {
        MethodA,
        MethodB,
        MethodC
    }
    public class ReflectionTest
    {

        public void Execute(ReflectionTestMethods method)
        {
            MethodInfo methodInfo = GetType().GetMethod(method.ToString()
                , BindingFlags.Instance | BindingFlags.NonPublic);
            if (methodInfo == null) throw new NotImplementedException(method.ToString());
            methodInfo.Invoke(this, null);
        }

        private void MethodA()
        {
            Debug.Print("MethodA");
        }

        private void MethodB()
        {
            Debug.Print("MethodB");
        }

        private void MethodC()
        {
            Debug.Print("MethodC");
        }
    }

但这可能是一个更好的解决方案...


    public class ActionTest
    {
        private readonly Dictionary _actions = new Dictionary();

        public ActionTest()
        {
            _actions.Add(ReflectionTestMethods.MethodA.ToString(), new Action(MethodA));
            _actions.Add(ReflectionTestMethods.MethodB.ToString(), new Action(MethodB));
            _actions.Add(ReflectionTestMethods.MethodC.ToString(), new Action(MethodC));
        }

        public void Execute(ReflectionTestMethods method)
        {
            if (!_actions.ContainsKey(method.ToString())) 
                throw new NotImplementedException(method.ToString());
            _actions[method.ToString()]();
        }

        private void MethodA()
        {
            Debug.Print("MethodA");
        }

        private void MethodB()
        {
            Debug.Print("MethodB");
        }
        private void MethodC()
        {
            Debug.Print("MethodC");
        }
    }

1

我使用了反射来让自己更灵活地满足不断变化的需求。也就是说,客户一直在改变他们想要在数据库中放置数据库表的位置。我所做的就是让对象自我检查其字段,并在对象本身内调用这些字段的对象构造函数。然后,如果应该在其他地方找到一个表?点击、粘贴、完成。

当然,这并没有在最终生产中使用,但在迭代阶段中去除了一些我需要更改的样板文件。


1

我使用反射来方便翻译我们表单上的标签和按钮等控件。使用反射,我会遍历表单上所有的控件,并将控件的名称、文本和标题写入 XML 文件。在 XML 中翻译完控件标题和文本后,读取文件并将每个控件的标题和文本设置为其翻译后的值。
我们的表单需要翻译成多种语言,使用反射帮助我们节省了很多时间。


0

我用它来:

  • 依赖注入
  • 解决 Microsoft 缺乏动力增加类似"协变/逆变泛型"和"带参数的new()约束"等功能的问题
  • 面向方面的编程 (在某种程度上,主要是使用PostSharp)

0

我只在生产代码中使用过一次反射。那是一个情况,我必须对一个启动例程中特定类方法的标准化使用进行调整(抱歉没有更具体的说明 - 那是很久以前,细节有些模糊)。解决问题的唯一方法是引入该方法的不同版本,并在运行时检查各种条件/代码条件等来确定应调用哪个方法。这是一个相当紧凑的代码片段,为本来可能是一个混乱的问题提供了简洁的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接