Base class for value objects

As a Roblox developer, it is currently difficult to tell whether or not something is a value object. Value objects such as “StringValue” do not inherit from a base class. This means to tell if something is a value object you either have to check the class name for each value object type, or check in a pcall if the Value property exists on the object (which can be unreliable).

Current method of checking:

local isValueObjTable = {
    BrickColorValue = true,
    CFrameValue = true,
    ObjectValue = true,
    IntValue = true,
    NumberValue = true,
    Vector3Value = true,
    RayValue = true,
    StringValue = true,
    Color3Value = true,
    BoolValue = true,
}

function isValueObj(instance)
    return isValueObjTable[instance.ClassName] ~= nil
end

If they were inheriting from a base “ValueObject” class then you could simply do:

Obj:IsA("ValueObject")

14 Likes

Alternatively…

local function IsValueObject(Obj)
    return Obj.ClassName:match('Value$') ~= nil;
end;

This also works. I agree there is a need for a base class, but it can be currently solved in this way, and it’s easy enough that it makes the need for it just a small quality of life improvement.

5 Likes

I believe the following is the preferred way to do it.

local function isValue(v)
  return v.ClassName:sub(-5) == "Value"
end
1 Like

The dictionary lookup is fastest. I find the pattern matching easiest to remember and write, but it’s the slowest. The substring is a reasonable string manipulation approach without using overkill patterns, but it’s not as fast as the lookup. :find is slightly faster than using :sub, but it doesn’t beat the dictionary lookup.

The results when running each 10 000 000 times:

   Lookup: 2.9121062755585
 Patterns: 4.3928997516632
Substring: 3.7693331241608
     Find: 3.6545922756195

I tested it multiple times and the results were consistent.

Code
local isValueObjTable = {
    BrickColorValue = true,
    CFrameValue = true,
    ObjectValue = true,
    IntValue = true,
    NumberValue = true,
    Vector3Value = true,
    RayValue = true,
    StringValue = true,
    Color3Value = true,
    BoolValue = true,
}

local function method1(instance)
    return isValueObjTable[instance.ClassName] ~= nil
end

local function method2(Obj)
    return Obj.ClassName:match('Value$') ~= nil
end

local function method3(v)
    return v.ClassName:sub(-5) == "Value"
end

local function method4(v)
    return v.ClassName:find("Value", 4, true) ~= nil
end

local value = script.Value
local function benchmark(func, name)
	local begin = tick()
	for i = 1, 10000000 do
		func(value)
	end
	local elapsed = tick() - begin
	print(name..": "..elapsed)
end

benchmark(method1, "   Lookup")
benchmark(method2, " Patterns")
benchmark(method3, "Substring")
benchmark(method4, "     Find")

Edit: Added :find for completeness.

2 Likes

All 5 feature request threads about this have involved a benchmark of sub/find/match, which is actually pretty impressive from a consistency standpoint.

Seriously though, I’ll bring it up internally.

11 Likes

Thx :heart:

2 Likes

This was added in version 335 back in 2018-04-24. Marking as solved for those who may be finding this in searches!

1 Like

This topic was automatically closed after 2 days. New replies are no longer allowed.