The beauty of Swift Language — Property Wrapper
In essence, a property of an object in OO languages can access to its backed storage directly or can have getter /setter to access to the backed storage indirectly. For the latter case, we can write logic needed in the getter/setter. To develop this idea further, what if we need to reuse the logic in getter/setter for other properties of this object and even properties of other objects? Proper Wrapper in Swift is invented for this purpose. Even though there’s no such concept in C# and Kotlin, but we can achieve the similar goal in those languages as well, which will be discussed in this article.
Swift
In this section, we will write a property wrapper for a Score object below. Its property math is Int type. However, as we all know, the score of math is within a range (like from 0 to 100) and its value should not go outside of this range. If we just define them as Int type, we might have wrong score for this subject(like -10 or 120 etc.). To avoid this kind of data error, we can add some check logic in setter like below:
struct Score {
private var _math: Int = 0
var math: Int {
get {
_math
}
set {
_math = max(min(newValue, 100), 0)
}
}
}
var myScore = Score()
myScore.math = 120
print(myScore.math) // will print out 100
This works well as we fixed the possible data error using max(min(newValue, 100), 0) in setter . However, if we need more properties to store scores of other subjects, we need to repeat this boilerplate code for each subject. Property Wrapper comes to rescue like below.
struct Score {
@ScoreRange var math: Int
@ScoreRange var physics: Int
@ScoreRange var chemistry: Int
}
@propertyWrapper
struct ScoreRange {
private var score = 0
var wrappedValue: Int {
get {
score
}
set {
score = max(min(newValue, 100), 0)
}
}
}
var myScore = Score()
myScore.math = 120
print(myScore.math) // will print out 100
myScore.physics = 120
print(myScore.physics) // will print out 100
myScore.chemistry = 120
print(myScore.chemistry) // will print out 100
In the above code, we define ScoreRange Property Wrapper and hold the setter logic and apply this wrapper to all the properties of the Score struct to achieve the code reuse purpose.
In actual code, we can write a more powerful property wrapper using generic to make it more flexible like below:
struct Score {
@Ranged(0...100) var math: Int = 0
@Ranged(0...120) var physics: Int = 0
@Ranged(0...150) var chemistry: Int = 0
}
@propertyWrapper
struct Ranged<T: Comparable> {
let range: ClosedRange<T>
var value: T
init(wrappedValue: T, \_ range: ClosedRange<T>) {
self.value = max(min(wrappedValue, range.upperBound), range.lowerBound)
self.range = range
}
var wrappedValue: T {
get {
value
}
set {
value = max(min(newValue, range.upperBound), range.lowerBound)
}
}
}
var myScore = Score()
myScore.math = 200
print(myScore.math) // will print out 100
myScore.physics = 200
print(myScore.physics) // will print out 120
myScore.chemistry = 200
print(myScore.chemistry) // will print out 150
More explanation about Swift property wrapper can be found in this article where also explained the Projected Value of Swift Property Wrappers.
C#
There’s no Property wrapper concept predefined in C#. The getter/setter might be the best choice if we want to add extra logic on getting/setting value to a property, hence it’s not directly supported to reuse the logic like the solution Property Wrapper offers in Swift. The most closest concept to it is [Attribute](https://learn.microsoft.com/en-us/dotnet/standard/attributes/) , which allows us to attach metadata to properties that can be extracted using runtime reflection services.
Please note that, regarding the functionality in the example code in Swift section to set a ranged value to a property, ASP.NET has defined a RangeAttribute Class to offer similar function.
Kotlin
Delegated property is a powerful feature in Kotlin that can be used for reusing common logic for properties. For this we just need to create a delegate class and let this class handle the common logic for the properties. The equivalent implementation of ScoreRange in Kotlin could look below:
import kotlin.reflect.KProperty
class Score {
var math: Int by ScoreRange(0)
var physics: Int by ScoreRange(0)
var chemistry: Int by ScoreRange(0)
}
class ScoreRange(private var value: Int = 0) {
operator fun getValue(thisRef: Any?, property: KProperty<\*>): Int {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<\*>, value: Int) {
this.value = minOf(100, maxOf(0, value))
}
}
fun main() {
val score = Score()
score.math = 120
println(score.math) // will print out 100
score.physics = 120
println(score.physics) // will print out 100
score.chemistry = 120
println(score.chemistry) // will print out 100
}
Of course, we can also implement its generic version Ranged like below:
import kotlin.reflect.KProperty
class Score {
var math: Int by Ranged(0, minValue = 0, maxValue = 100)
var physics: Int by Ranged(0, minValue = 0, maxValue = 120)
var chemistry: Int by Ranged(0, minValue = 0, maxValue = 150)
}
class Ranged<T: Comparable<T>>(private var value: T, private val minValue: T, private val maxValue: T) {
operator fun getValue(thisRef: Any?, property: KProperty<\*>): T {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<\*>, value: T) {
this.value = minOf(maxValue, maxOf(minValue, value))
}
}
fun main() {
val score = Score()
score.math = 200
println(score.math) // will print out 100
score.physics = 200
println(score.physics) // will print out 120
score.chemistry = 200
println(score.chemistry) // will print out 150
}
Conclusion
By comparison, we can clearly see that, while C# doesn’t have native solution to reuse common logic for properties, both Swift and Kotlin have native solutions to it and it’s called Property Wrapper in Swift and Delegate Property in Kotlin.
Please also note that, because of the design differences, while they can be used for common logics for properties, these language features are not completely equal. Like Swift property wrapper has projected value that is widely used in SwiftUI while [Delegate Property](https://kotlinlang.org/docs/delegated-properties.html) in Kotlin has other use cases too.
Sharing is caring!