{"record":{"id":"3480ce59e07aa8fb","repo":"ppy/osu","slug":"setting-nameof-currentskin-s-value-directly-is","errorCode":null,"errorMessage":"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.","messagePattern":"Setting (.+?)'s value directly is not supported\\. Use (.+?) instead\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Skinning/SkinManager.cs","lineNumber":129,"sourceCode":"            realm.Write(r =>\r\n            {\r\n                foreach (var skin in defaultSkins)\r\n                {\r\n                    if (r.Find<SkinInfo>(skin.SkinInfo.ID) == null)\r\n                        r.Add(skin.SkinInfo.Value);\r\n                }\r\n            });\r\n\r\n            CurrentSkinInfo.ValueChanged += skin =>\r\n            {\r\n                CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin);\r\n            };\r\n\r\n            CurrentSkin.Value = argonSkin;\r\n            CurrentSkin.ValueChanged += skin =>\r\n            {\r\n                if (!skin.NewValue.SkinInfo.Equals(CurrentSkinInfo.Value))\r\n                    throw new InvalidOperationException($\"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.\");\r\n\r\n                SourceChanged?.Invoke();\r\n            };\r\n\r\n            skinExporter = new LegacySkinExporter(storage)\r\n            {\r\n                PostNotification = obj => PostNotification?.Invoke(obj)\r\n            };\r\n        }\r\n\r\n        /// <summary>\r\n        /// Returns the dropdown ordering for use mainly by the skin selection UI.\r\n        /// Inserts the defaults first, then 'random skin', then custom ones.\r\n        /// Returns a list of <see cref=\"Live{SkinInfo}\"/> items.\r\n        /// </summary>\r\n        public IList<Live<SkinInfo>> GetAllUsableSkins()\r\n        {\r\n            var skins = new List<Live<SkinInfo>>();\r","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Skinning/SkinManager.cs#L111-L147","documentation":"Thrown by the CurrentSkin.ValueChanged handler when a new Skin is assigned to CurrentSkin whose SkinInfo does not equal CurrentSkinInfo.Value. The API contract is that skin switching must go through CurrentSkinInfo (the source of truth), not CurrentSkin (the derived live instance). Directly setting CurrentSkin bypasses reload logic.","triggerScenarios":"Code sets skinManager.CurrentSkin.Value = someSkin directly, bypassing CurrentSkinInfo. The change handler detects the mismatch with CurrentSkinInfo and rejects it.","commonSituations":"Porting skin-switching code from a system that manipulated a live Skin value; copy-paste from another Bindable pattern; an extension/component that grabbed CurrentSkin and tried to drive it.","solutions":["Switch skins via CurrentSkinInfo.Value = skinInfo instead of mutating CurrentSkin.","Read CurrentSkin only for display; never assign to it.","If you must mutate a live skin, ensure it belongs to the current CurrentSkinInfo first."],"exampleFix":"// before\nskinManager.CurrentSkin.Value = mySkin;\n\n// after\nskinManager.CurrentSkinInfo.Value = mySkin.SkinInfo;","handlingStrategy":"validation","validationCode":"// Never assign CurrentSkin directly; always go through CurrentSkinInfo.\nskinManager.CurrentSkinInfo.Value = newSkinInfo;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat CurrentSkin as read-only.","Switch skins only via CurrentSkinInfo.","Audit code that holds the CurrentSkin bindable."],"tags":["skinning","skin-manager","bindable","api-misuse"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}