Golang Go语言中分享:泛型+context 自动根据类型生成 ctx key

package gctx

import ( “context” “fmt” “reflect” )

type body[T any] struct { Value T }

type typeKey[T any] struct { }

func Put[T any](ctx context.Context, value T) context.Context { key := typeKey[body[T]]{} return context.WithValue(ctx, key, body[T]{Value: value}) }

func Get[T any](ctx context.Context) (T, error) { key := typeKey[body[T]]{} value, ok := ctx.Value(key).(body[T]) if !ok { var zero T return zero, errors.New(“not found”) } return value.Value, nil }


Golang Go语言中分享:泛型+context 自动根据类型生成 ctx key

更多关于Golang Go语言中分享:泛型+context 自动根据类型生成 ctx key的实战教程也可以访问 https://www.itying.com/category-94-b0.html

17 回复

这种代码写的误人子弟。。
官方 value context 的使用说明你是一点没读啊。

更多关于Golang Go语言中分享:泛型+context 自动根据类型生成 ctx key的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


额请教一下,什么问题 确实没读……

老实说有点不爽,确实是我学艺不精,这个是我的问题,但是您的评论是不是有点尖锐了……

官方推荐 type alias 就挺简单的

我要取一个具体的 interface 出来呢?比如我只要 type interface Logger{Info(any)},我不关心具体类型,你这样要存进去还是要做额外处理

没太看明白,类型做 key ?每种类型只能存一个数据?


哥们我既没人身攻击也没任何脏字。。你这还是有点脆弱了啊。
搁 Linus Torvalds 这种攻击性拉满的,就该直接骂你代码是 trash 了(斜眼笑

不过给个建议,写偏基础性质的代码,务必搞懂每一行代码的作用和大致原理,尤其是参数上的一些设计,你写的时候就不好奇为什么 value context 的 key 和 value 都是 interface 类型么。
不然就会有你这连 value context 设计和原理都没读的闹的笑话了。

对 因为我用这个方法主要存的是 *Struct 基本不存在需要一个结构体需要存多份的情况

……好吧 因为我的需求只是存*Struct 基本每个 ctx 只需要存一份 所以我这个是挺满足我自己的需求的


我碰到很多也不读 value context 设计的研发,用的时候直接 key=“keyString”,然后 value 直接拐上去了。
放你这个设计里是直接 boom 。

我这个专门放 *struct 还有 interface 的 我每个类型只放一次 如果那种的肯定不用这个方案了

我这个只是为了那种只存放一个类型的 避免手写 key

依然可用 直接存 只是要注意 存进去时的类型 一定要是 Logger

<br>package gctx<br><br>import (<br> "context"<br> "log"<br> "testing"<br>)<br><br>// MockLogger 是一个实现 Logger 接口的模拟结构体<br>type MockLogger struct {<br> lastMessage any<br>}<br><br>func (m *MockLogger) Info(msg any) {<br> log.Println("MockLogger:", msg)<br> m.lastMessage = msg<br>}<br><br>type Logger interface {<br> Info(msg any)<br>}<br><br>func TestLoggerContextStorage(t *testing.T) {<br> // 创建一个基础 context<br> ctx := context.Background()<br><br> // 创建一个 MockLogger 实例<br> logger := &amp;MockLogger{}<br><br> // 将 logger 存储到 context 中<br> ctx = Put(ctx, Logger(logger))<br><br> // 从 context 中检索 logger<br> retrievedLogger, err := Get[Logger](ctx)<br> if err != nil {<br> t.Fatalf("Failed to retrieve logger from context: %v", err)<br> }<br> <a target="_blank" href="http://retrievedLogger.Info" rel="nofollow noopener">retrievedLogger.Info</a>("Test log message")<br> // 检查检索到的 logger 是否是原始 logger<br> if retrievedLogger != logger {<br> t.Errorf("Retrieved logger is not the same as the original logger")<br> }<br><br> // 使用检索到的 logger<br> testMessage := "Test log message"<br> <a target="_blank" href="http://retrievedLogger.Info" rel="nofollow noopener">retrievedLogger.Info</a>(testMessage)<br><br> // 验证消息是否正确记录<br> if logger.lastMessage != testMessage {<br> t.Errorf("Expected last message to be %q, but got %q", testMessage, logger.lastMessage)<br> }<br><br> // 测试检索不存在的类型<br> _, err = Get[string](ctx)<br> if err == nil {<br> t.Error("Expected error when retrieving non-existent type, but got nil")<br> }<br>}<br><br>


血的教训告诉你约束要做在编译器上,而不是口头上

emmm 好的

一楼说的没错,你这确实误人子弟。建议阅读官方 context 文档: https://pkg.go.dev/context#WithValue

一些大型项目/框架中的血泪史:
- https://github.com/gin-gonic/gin/issues/3888
- https://github.com/gin-gonic/gin/issues/4040


最后建议在帖子后面追加说明吧,不然这真的误人子弟

go 是怎么判断两个变量是==的?

在Go语言中,泛型与context的结合使用可以极大提升代码的复用性和类型安全性。当你需要在context中传递不同类型的数据时,为每个类型手动生成一个唯一的key可能会显得繁琐且容易出错。利用泛型,我们可以自动化这一过程。

首先,我们需要理解context.WithValue函数需要一个interface{}类型的key,这通常是通过一个未导出的变量来实现的,以避免key冲突。然而,手动为每个类型创建这样的变量并不优雅。

利用泛型,我们可以创建一个辅助函数,自动生成一个基于类型的唯一key。这里是一个简单的实现思路:

package main

import (
	"context"
	"fmt"
	"reflect"
)

// NewContextKey 生成基于类型的唯一key
func NewContextKey[T any]() interface{} {
	return struct{}{typeKey: reflect.TypeOf(T{}).String()}{}
}

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, NewContextKey[string](), "example")
	
	// 从context中获取值
	val := ctx.Value(NewContextKey[string]()).(string)
	fmt.Println(val) // 输出: example
}

注意,由于interface{}不能直接作为map的key,我们使用了一个包含匿名字段的结构体来绕过这一限制。这里的typeKey字段实际上并不存储值,只是利用了其类型信息来确保唯一性。

这种方法使得我们可以根据类型自动生成唯一的context key,提高了代码的可读性和维护性。

回到顶部