See the question and my original answer on StackOverflow

You can build a wrapper/proxy class based on the ICustomTypeDescriptor Interface that allows you to tweak properties at runtime.

This is how you could use it:

var a = new A();

// build a proxy
var proxy = new Proxy(a);

// tweak any properties
proxy.Properties["Y"].IsReadOnly = false;
// you can also tweak attributes
proxy.Properties["Y"].Attributes.Add(new CategoryAttribute("R/O -> R/W"));
proxy.Properties["Y"].Attributes.Add(new DescriptionAttribute("This works"));

// handle property change
propertyGrid1.PropertyValueChanged += (s, e) =>
{
    if (e.ChangedItem.PropertyDescriptor.Name == "Y")
    {
        a.Y = (int)e.ChangedItem.Value;
    }
};

// select the proxy instead of the original instance
propertyGrid1.SelectedObject = proxy;

And here is the result

enter image description here

...
class A
{
    public int X { get; set; }
    public int Y { get; internal set; }
}

...


public class Proxy : ICustomTypeDescriptor
{
    public Proxy(object instance)
    {
        if (instance == null)
            throw new ArgumentNullException(nameof(instance));

        Instance = instance;
        Properties = TypeDescriptor.GetProperties(instance).OfType<PropertyDescriptor>().Select(d => new ProxyProperty(instance, d)).ToDictionary(p => p.Name);
    }

    public object Instance { get; }
    public IDictionary<string, ProxyProperty> Properties { get; }

    public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(Instance);
    public string GetClassName() => TypeDescriptor.GetClassName(Instance);
    public string GetComponentName() => TypeDescriptor.GetComponentName(Instance);
    public TypeConverter GetConverter() => TypeDescriptor.GetConverter(Instance);
    public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(Instance);
    public object GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(Instance, editorBaseType);
    public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(Instance);
    public EventDescriptorCollection GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(Instance, attributes);
    public PropertyDescriptor GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(Instance);
    public PropertyDescriptorCollection GetProperties() => new PropertyDescriptorCollection(Properties.Values.Select(p => new Desc(this, p)).ToArray());
    public PropertyDescriptorCollection GetProperties(Attribute[] attributes) => GetProperties();
    public object GetPropertyOwner(PropertyDescriptor pd) => Instance;

    private class Desc : PropertyDescriptor
    {
        public Desc(Proxy proxy, ProxyProperty property)
            : base(property.Name, property.Attributes.ToArray())
        {
            Proxy = proxy;
            Property = property;
        }

        public Proxy Proxy { get; }
        public ProxyProperty Property { get; }

        public override Type ComponentType => Proxy.GetType();
        public override Type PropertyType => Property.PropertyType ?? typeof(object);
        public override bool IsReadOnly => Property.IsReadOnly;
        public override bool CanResetValue(object component) => Property.HasDefaultValue;
        public override object GetValue(object component) => Property.Value;
        public override void ResetValue(object component) { if (Property.HasDefaultValue) Property.Value = Property.DefaultValue; }
        public override void SetValue(object component, object value) => Property.Value = value;
        public override bool ShouldSerializeValue(object component) => Property.ShouldSerializeValue;
    }
}

public class ProxyProperty
{
    public ProxyProperty(string name, object value)
    {
        if (name == null)
            throw new ArgumentNullException(nameof(value));

        Name = name;
        Value = value;
        Attributes = new List<Attribute>();
    }

    public ProxyProperty(object instance, PropertyDescriptor descriptor)
    {
        if (descriptor == null)
            throw new ArgumentNullException(nameof(descriptor));

        Name = descriptor.Name;
        Value = descriptor.GetValue(instance);
        var def = descriptor.Attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
        if (def != null)
        {
            HasDefaultValue = true;
            DefaultValue = def.Value;
        }

        IsReadOnly = (descriptor.Attributes.OfType<ReadOnlyAttribute>().FirstOrDefault()?.IsReadOnly).GetValueOrDefault();
        ShouldSerializeValue = descriptor.ShouldSerializeValue(instance);
        Attributes = descriptor.Attributes.Cast<Attribute>().ToList();
        PropertyType = descriptor.PropertyType;
    }

    public string Name { get; }
    public object Value { get; set; }
    public object DefaultValue { get; set; }
    public bool HasDefaultValue { get; set; }
    public bool IsReadOnly { get; set; }
    public bool ShouldSerializeValue { get; set; }
    public Type PropertyType { get; set; }
    public IList<Attribute> Attributes { get; }
}