分享:泛型+context 自动根据类型生成 ctx key

7 天前
 8520ccc
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
}

962 次点击
所在节点    Go 编程语言
16 条回复
povsister
7 天前
这种代码写的误人子弟。。
官方 value context 的使用说明你是一点没读啊。
8520ccc
6 天前
@povsister 额请教一下,什么问题 确实没读……
8520ccc
6 天前
@povsister 老实说有点不爽,确实是我学艺不精,这个是我的问题,但是您的评论是不是有点尖锐了……
tairan2006
6 天前
官方推荐 type alias 就挺简单的
lrh3321
6 天前
我要取一个具体的 interface 出来呢?比如我只要 type interface Logger{Info(any)},我不关心具体类型,你这样要存进去还是要做额外处理
dingyaguang117
6 天前
没太看明白,类型做 key ?每种类型只能存一个数据?
povsister
6 天前
@8520ccc
哥们我既没人身攻击也没任何脏字。。你这还是有点脆弱了啊。
搁 Linus Torvalds 这种攻击性拉满的,就该直接骂你代码是 trash 了(斜眼笑

不过给个建议,写偏基础性质的代码,务必搞懂每一行代码的作用和大致原理,尤其是参数上的一些设计,你写的时候就不好奇为什么 value context 的 key 和 value 都是 interface 类型么。
不然就会有你这连 value context 设计和原理都没读的闹的笑话了。
8520ccc
6 天前
@dingyaguang117 对 因为我用这个方法主要存的是 *Struct 基本不存在需要一个结构体需要存多份的情况
8520ccc
6 天前
@povsister ……好吧 因为我的需求只是存*Struct 基本每个 ctx 只需要存一份 所以我这个是挺满足我自己的需求的
povsister
6 天前
@8520ccc
我碰到很多也不读 value context 设计的研发,用的时候直接 key="keyString",然后 value 直接拐上去了。
放你这个设计里是直接 boom 。
8520ccc
6 天前
@povsister 我这个专门放 *struct 还有 interface 的 我每个类型只放一次 如果那种的肯定不用这个方案了

我这个只是为了那种只存放一个类型的 避免手写 key
8520ccc
6 天前
@lrh3321 依然可用 直接存 只是要注意 存进去时的类型 一定要是 Logger

```
package gctx

import (
"context"
"log"
"testing"
)

// MockLogger 是一个实现 Logger 接口的模拟结构体
type MockLogger struct {
lastMessage any
}

func (m *MockLogger) Info(msg any) {
log.Println("MockLogger:", msg)
m.lastMessage = msg
}

type Logger interface {
Info(msg any)
}

func TestLoggerContextStorage(t *testing.T) {
// 创建一个基础 context
ctx := context.Background()

// 创建一个 MockLogger 实例
logger := &MockLogger{}

// 将 logger 存储到 context 中
ctx = Put(ctx, Logger(logger))

// 从 context 中检索 logger
retrievedLogger, err := Get[Logger](ctx)
if err != nil {
t.Fatalf("Failed to retrieve logger from context: %v", err)
}
retrievedLogger.Info("Test log message")
// 检查检索到的 logger 是否是原始 logger
if retrievedLogger != logger {
t.Errorf("Retrieved logger is not the same as the original logger")
}

// 使用检索到的 logger
testMessage := "Test log message"
retrievedLogger.Info(testMessage)

// 验证消息是否正确记录
if logger.lastMessage != testMessage {
t.Errorf("Expected last message to be %q, but got %q", testMessage, logger.lastMessage)
}

// 测试检索不存在的类型
_, err = Get[string](ctx)
if err == nil {
t.Error("Expected error when retrieving non-existent type, but got nil")
}
}

```
povsister
6 天前
@8520ccc
血的教训告诉你约束要做在编译器上,而不是口头上
8520ccc
6 天前
@povsister emmm 好的
FarmerChillax
6 天前
一楼说的没错,你这确实误人子弟。建议阅读官方 context 文档: https://pkg.go.dev/context#WithValue

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


最后建议在帖子后面追加说明吧,不然这真的误人子弟
edcopclub
6 天前
go 是怎么判断两个变量是==的?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1076403

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX