One solution at least worth considering is to use the type system:
interface IObject{}
interface IContainer : IObject{
MaterialType Material {get;}
}
interface ILiquidContainer : IContainer {}
interface ISolidContainer : IContainer {}
interface IEdible : IObject{}
interface IFruit : IEdible {get;}
class Apple : IFruit{}
This has some advantages. It lets you express relationships like "an Apple is a fruit" or "a Container has a material". It uses the type system to ensure the type model is followed, so an apple cannot have a Material.
There are also disadvantages. It is probably not appropriate if you want to define your objects, relationships and rules dynamically.
Whatever model or rules you create you will always risk problems and conflicts. You say "cans are metal objects and containers that hold liquid", but a watering can may be made of plastic. Or "Cans" may refer to headphones. Human language can be messy, inconsistent and ambiguous, so if you are working with human labeled data you likely want to take this into account.