Golang 关于反射机制(一文了解)

这篇具有很好参考价值的文章主要介绍了Golang 关于反射机制(一文了解)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

Golang 反射比 C++ RTTI 要强大的多,但是比 .NET C#/VB/C++ 来说,它大约属于低阶反射支持的范畴。

但是 Golang 语言提供了相对强大的反射。

它总要比没有提供易用反射支持的要好的多,C++ 之中我们基本只能依赖模板、宏通过元编程来实现相对强大的反射机制。

Golang 反射弱的原因:

1、没有强大的动态编程,Emit 反射发出机制,

     这会导致,很难实现真正意义上的 “Dynamic Proxy 动态代理框架”,这常常用于AOP切面编程,但的确可以通过如生成代码,或其它的解决方案来代替。

2、没有强大的动态表达式树编程,即:System.Linq.Expressions

    相对于 Emit 嵌入平台IL(IL Assembly)来说要相对更舒服一些,但其接口的缺点是,拽写动态表达式 CodeDom 树比嵌入(IL Assembly)还要晦涩难懂一些,但学好规约树,深入理解计算机编程本质的童鞋,不会存在上手太困难的问题。

但是鱼合掌不可兼得,Golang 是一门AOT静态编译的语言,的确很难令反射支持 “动态反射并构建 Emit”。

这或许没有办法,但反射支持 Emit 动态编程才算是 “高阶反射”,否则都属于中低阶反射机制,这确实大家认同且不争的事实。

让我们展望一下下述例程代码:

反射:获取字段值、设置字段值

type Person struct {
	Name string
	Age  int
}

func main() {
	p := Person{"小明", 20}
	t := reflect.TypeOf(p)
	fmt.Println("类型名:", t.Name())

	// 获取值信息
	v := reflect.ValueOf(p)
	fmt.Println("值信息:", v)

	// 修改值信息
	v = reflect.ValueOf(&p).Elem()
	v.FieldByName("Name").SetString("Bob")
	v.FieldByName("Age").SetInt(30)
	fmt.Println("修改值:", p)
}

1、它定义一个 Person 类型,其类型描述了人的名字跟年龄,我们首先构造一个 Person 对象的实例,并且通过 reflect.TypeOf(any) 函数来获取它的反射类型信息,并输出它的类型名到控制台窗口上面。

2、通过 reflect.ValueOf(p) 函数获取实例 p 的值信息,并将其打印到控制台上面。

3、通过 reflect.ValueOf(&p).Elem() 函数获取对象的所有可见成员。

注意:

Golang 反射不可以 “侵入式访问私有的成员” (即反射:侵入式编程)。

在 Golang 之中成员声明首字母不为大写字符,即:“^[A-Z]” 那么则被 Golang 编译器视为私有的内部成员。

并且通过 FieldByName 函数查找其类型可见成员字段并且设置它的值。

本人需要提一嘴,反射访问效能无论在哪个语言之中都会是一个大的损耗问题,在 C# 语言之中,不优化的情况下,反射效率性能至少相对正常访问代码差10倍以上,但深入优化之后可以减少到1倍甚至更低。

JAVA 语言反射相对 C# 语言反射效率更差,但好歹可以从字节码上面优化,C# 做到1倍甚至更低是需要动态构建函数的。(高阶反射:动态编程)

所以:一个合格的开发人员必须考虑提高反射成员的效率,那么最差的情况,你至少可以减少对于查找成员的开销。

例如:

在 Go 语言运行时库之中实现的 reflect::type.go 文件,对于 FieldByName 基础框架库函数的内部实现为:

// FieldByName returns the struct field with the given name
// and a boolean to indicate if the field was found.
func (t *structType) FieldByName(name string) (f StructField, present bool) {
	// Quick check for top-level name, or struct without embedded fields.
	hasEmbeds := false
	if name != "" {
		for i := range t.Fields {
			tf := &t.Fields[i]
			if tf.Name.Name() == name {
				return t.Field(i), true
			}
			if tf.Embedded() {
				hasEmbeds = true
			}
		}
	}
	if !hasEmbeds {
		return
	}
	return t.FieldByNameFunc(func(s string) bool { return s == name })
}

可见,如若不优化反射效率,在一秒钟内成千上万次的重复执行之中,无意义的效能损耗,是多么恐怖、没有意义与价值的。

在曾经 .NET 未开源的时候,人们只有依靠 ILdasm、ILSpy、dnspy++、.Net Reflector、DEIL 这些反编译的工具来分析基础框架库的代码,来判断那些可能会有潜在的问题,但这个问题不应该出现在开源的 Go 语言之中,一个专整某门高级语言的开发人员,其对语言的基础库实现一无所知是很可怕的事情。

就像早前的 C/C++ 开发人员,有多少人是呕心沥血,熬了多少个通宵,不眠夜的玩 IDA、OD、Ghidra,等这些工具,硬着头皮逆向别人程序进行反汇编来学习别人的高级玩法及程式实现的,又有多少的前辈是在技术研究的汪洋大海之中烟消玉殒的,比如国内最早研究 ffmpeg 及流媒体的大神,大家知道是那位先行者,唯有惋惜。

反射调用函数有哪些形式?

1、调用外部函数

func Add(a, b int) int {
	return a + b
}

func main() {
	// 获取函数信息
	funcValue := reflect.ValueOf(Add)

	// 创建参数列表
	args := []reflect.Value{
		reflect.ValueOf(10),
		reflect.ValueOf(20),
	}

	// 调用函数
	result := funcValue.Call(args)

	// 获取返回值
	sum := result[0].Int()
	fmt.Println("返回值:", sum)
}

2、调用成员函数

type Person struct {
	Name string
	Age  int
}

func (p *Person) SayHello() int {
	fmt.Printf("你好, 我的名字是 %s,当前年龄已经 %d 岁了.\n", p.Name, p.Age)
	return 1
}

func main() {
	p := &Person{
		Name: "小明",
		Age:  25,
	}

	// 获取方法信息
	methodValue := reflect.ValueOf(p).MethodByName("SayHello")

	// 调用方法
	result := methodValue.Call(nil)
	sum := result[0].Int()
	fmt.Println("返回值:", sum)
}

如何通过反射函数的返回值类型?

func Add(a, b int) (int64, error) {
	return int64(a) + int64(b), nil
}

func main() {
	// 获取函数信息
	funcValue := reflect.ValueOf(Add)
	funcType := funcValue.Type()

	// 获取返回值数量
	numOut := funcType.NumOut()

	// 遍历返回值类型
	for i := 0; i < numOut; i++ {
		returnType := funcType.Out(i)
		fmt.Println("返回值类型:", returnType)
	}
}

 

如果通过反射实例化一个类的对象实例?

type Person struct {
	Name string
	Age  int
}

func main() {
	// 获取类型信息
	personType := reflect.TypeOf(Person{})

	// 创建对象
	personPtrValue := reflect.New(personType)
	personValue := personPtrValue.Elem()

	// 设置属性值
	nameField := personValue.FieldByName("Name")
	nameField.SetString("小名")

	ageField := personValue.FieldByName("Age")
	ageField.SetInt(25)

	// 转换为接口类型
	person := personValue.Interface().(Person)

	fmt.Println(person)
}

大家似乎发现了一些问题,为什么不能像:

1、C/C++ 通过XXX来获取类型信息?

1.1、typeid(T)

1.2、Type ^t = Int32::typeid;

2、C# 通过 typeof(T) 来获取类型信息。

而是必须先构建一个类型实例,在通过实例去获取类型的信息,就像人们在 JAVA 之中调用对象实例的 .getClass() 函数,或者C#语言之中调用对象的 .GetType() 函数来获取其类型信息。

这无疑是浪费了一定的内存,只要人们去构建对象就需要分配这个对象持有的内存资源,明明应当提供直接获取类型信息的方法才对,对此我的确表示;不太理解。

即便是JAVA那类让我感到些许讨厌的语言也提供 T.class 的语法来获取T的类型信息,但在Go语言之中是暂不可行的。

如果通过反射类字段上面的 Tag(标记),其实标记这个概念,真的令我理解的有些稍许蛋疼,其实更习惯理解为在 C++、C# 语言之中,名为 “Attribute 特性” 的概念。

例如:

1.1、GUN C/C++

设置变量内存对齐

int tmp __attribute__ ((aligned (16))) = 0;

1.2、VC++ 

typedef __declspec(align(8)) struct { 
    int age; 
};

 2、C#

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool MoveNext() {
    return false;
}

在 Golang 语言之中,我们可以通过以下方法来反射并且打印类型字段上面被 Tag 标记的值。

type Person struct {
	Name string `json:"name" db:"名字"`
	Age  int    `json:"age" db:"年龄"`
}

func main() {
	p := Person{
		Name: "小明",
		Age:  25,
	}

	// 获取类型信息
	personType := reflect.TypeOf(p)

	// 遍历字段
	for i := 0; i < personType.NumField(); i++ {
		field := personType.Field(i)
		tag := field.Tag

		// 获取标签信息
		jsonTag := tag.Get("json")
		dbTag := tag.Get("db")

		fmt.Printf("字段名: %s, JSON标记: %s, DB标记: %s\n", field.Name, jsonTag, dbTag)
	}
}

这大约就是 Golang 提供的反射系统机制,可以为开发人员提供的支援,仔细思虑,以 Golang 语言本身的反射机制,大约可以用于设计并实现那些 “FCL/基础框架” 场景呢?

1、ORM(数据对象映射框架)

2、DI(依赖注入框架)

3、AOP(面向切面编程框架,分离日志、安全审计等) 

     但需要面向接口编程,人们可以手动实现 Redirector 重定向代理

4、基于反射运行时的序列化(如:XML、JSON、二进制)

5、IoC(控制反转框架)

6、BDD 行为驱动测试及 TDD 测试驱动开发

7、Web 应用服务器路由框架(如:MVC)

更多的情况恕我孤陋寡闻, 人们可以查漏补缺,但反射在实际解决方案之中的大多数用途,通用被应用于这几个方面。文章来源地址https://www.toymoban.com/news/detail-817178.html

到了这里,关于Golang 关于反射机制(一文了解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Golang】一文学完 Golang 基本语法

    安装包链接:https://share.weiyun.com/InsZoHHu IDE 下载:https://www.jetbrains.com/go/ 每个可执行代码都必须包含 Package、import 以及 function 这三个要素。 主函数文件: package1 包的文件: 注意:golang是 以首字母大小写 来区分对包外是否可见。 所以 Fun() 函数,Str,想要在 main 文件被访问,

    2024年02月13日
    浏览(45)
  • 一文搞懂Golang中的接口

    目录 接口 接口介绍 接口定义 实现接口 空接口 实现空接口 类型断言 使用类型断言 结构体实现多接口 接口嵌套 结构体值接收者和指针接收者实现接口的区别 代码案例 Go语言中的接口(interface)是一组方法签名的集合,是一种抽象类型。接口定义了方法,但没有实现,而是

    2024年02月06日
    浏览(39)
  • 深入了解Golang atomic原子操作

       在编程中经常遇到并发而产生的问题,那么应该怎么解决并发呢?什么情况下会产生并发以及为什么会有并发问题的产生?下面我将从宏观的角度讲解并发安全性问题。    并发是指在同一时间间隔内,多个任务(线程、进程或活动)同时存在和执行的状态,在同一个

    2024年02月21日
    浏览(50)
  • 一文掌握 Golang 模糊测试(Fuzz Testing)

    模糊测试(Fuzz Testing)是通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。可以用来发现应用程序、操作系统和网络协议等中的漏洞或错误,特别是容易被忽视的边界情况。模糊测试的基本思路是在测试过程中生成大量的随机数,然后将这些数据输入

    2024年02月07日
    浏览(40)
  • Golang 中的信号(Signal)机制详解

    目录 信号基础概念 Golang 对信号的处理 信号处理的使用场景和使用示例 信号的局限性 Go 中的特殊信号处理 小结 Signal 是一种操作系统级别的事件通知机制,进程可以响应特定的系统信号。这些信号用于指示进程执行特定的操作,如程序终止、挂起、恢复等。Golang 的标准库

    2024年01月16日
    浏览(42)
  • 关于golang的定时任务

    本技术文档旨在介绍在 Golang(或称为 Go)编程语言中实现定时任务的方法和最佳实践。定时任务是开发中常见的需求,可以用于周期性的任务调度、定时数据备份、定时报表生成等。Golang 提供了多种方式来实现定时任务,包括使用标准库中的定时器、第三方库以及基于调度

    2024年02月09日
    浏览(40)
  • 1分钟带你了解golang(go语言)

    Golang:也被称为Go语言,是一种开源的编程语言。由Google的Robert Griesemer、Rob Pike和Ken Thompson于2007年开始设计,2009年11月正式对外发布。(被誉为21世纪的C语言) 像python一样的优雅,有c一样的性能 天生的协程 编译快 … 编辑器: goland(推荐) 下载链接 vscode 官网:golang.goog

    2024年02月03日
    浏览(43)
  • 关于golang锁的一点东西

    本文基于go 1.19.3 最近打算再稍微深入地看下golang的源码,先从简单的部分入手。正巧前段时间读了操作系统同步机制的一点东西,那么golang这里就从锁开始好了。 在这部分内容中,可能不会涉及到太多的细节的讲解。更多的内容会聚焦在我感兴趣的一些点,以及整体的设计

    2024年02月14日
    浏览(42)
  • GoLang学习之路,对Elasticsearch的使用,一文足以(包括泛型使用思想)(二)

    书写上回,上回讲到,Elasticsearch的使用前提即:语法,表结构,使用类型结构等。要学这个必须要看前面这个:GoLang学习之路,对Elasticsearch的使用,一文足以(包括泛型使用思想)(一),因为这篇是基础!!!!!!! 必须要有一个 ElasticSearch 服务器 必须要有一个可视化

    2024年02月04日
    浏览(38)
  • 【MySQL】一文带你彻底了解事务机制

    我们设想一个场景,这个场景中我们需要插入多条相关联的数据到数据库,不幸的是,这个过程可能会遇到下面这些问题: 数据库中途突然因为某些原因挂掉了。 客户端突然因为网络原因连接不上数据库了。 并发访问数据库时,多个线程同时写入数据库,覆盖了彼此的更改

    2024年02月09日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包