有些东西是语法糖,有些东西不是!不是说你能实现相同功能,别人就都变语法糖了。举个例子,in、out、ref 参数涉及 GC 对内部指针的支持。JVM 的 GC 不支持。那它的库 API 设计势必迁就这一点。例如原子性原语,Java API 只能人工将变量/字段擢升后用 AtomicInteger 的那种方法操纵数据,那还算优雅吗?.NET runtime 上的 C# 真的优雅太多了。当然理论上 Java 也可以加糖,让编译器帮你搞定。但现实就是不存在的,以后也不会存在的。还是这个特性,C++/CLI 是复用 const 关键字,引入托管引用 %,和增加类型 interior_ptr 来解决的。增加了 0 个关键字。 照题主的逻辑,最规整统一的 C++/CLI 反而因为关键字少最不优雅?什么道理,完全说不通嘛。
C# 就是奔着托管世界的 C/C++ 去的。它对底层的把握是极为精细的。Java、Python 都没这种能力。C# 的边界就是托管世界的工业化语言的开拓前缘。别跟我说什么 Scala 是 JVM 上的 C++,我完全不信。
一些地方变丑,主要原因还是因为底层的复杂性。来看看 C# 新语法——函数指针:- //This method has a managed calling convention. This is the same as leaving the managed keyword off.
- delegate* managed<int, int>;
- // This method will be invoked using whatever the default unmanaged calling convention on the runtime
- // platform is. This is platform and architecture dependent and is determined by the CLR at runtime.
- delegate* unmanaged<int, int>;
- // This method will be invoked using the cdecl calling convention
- // Cdecl maps to System.Runtime.CompilerServices.CallConvCdecl
- delegate* unmanaged[Cdecl] <int, int>;
- // This method will be invoked using the stdcall calling convention, and suppresses GC transition
- // Stdcall maps to System.Runtime.CompilerServices.CallConvStdcall
- // SuppressGCTransition maps to System.Runtime.CompilerServices.CallConvSuppressGCTransition
- delegate* unmanaged[Stdcall, SuppressGCTransition] <int, int>;
复制代码 一个关键字也没增加,但就是丑了吧唧的。优不优雅和关键字的多少,我看就没有关系。- public unsafe readonly struct EFI_CREATE_EVENT {
- readonly delegate* unmanaged[Cdecl]<UINT32, EFI_TPL, EFI_EVENT_NOTIFY, void*, EFI_EVENT*, EFI_STATUS> value__;
- EFI_CREATE_EVENT(delegate* unmanaged[Cdecl]<UINT32, EFI_TPL, EFI_EVENT_NOTIFY, void*, EFI_EVENT*, EFI_STATUS> value) => value__ = value;
- public static implicit operator delegate* unmanaged[Cdecl]<UINT32, EFI_TPL, EFI_EVENT_NOTIFY, void*, EFI_EVENT*, EFI_STATUS>(EFI_CREATE_EVENT value) => value.value__;
- public static implicit operator EFI_CREATE_EVENT(delegate* unmanaged[Cdecl]<UINT32, EFI_TPL, EFI_EVENT_NOTIFY, void*, EFI_EVENT*, EFI_STATUS> value) => new EFI_CREATE_EVENT(value);
- /// <summary>
- /// Creates an event.
- /// </summary>
- /// <param name=&#34;Type&#34;>The type of event to create and its mode and attributes.</param>
- /// <param name=&#34;NotifyTpl&#34;>The task priority level of event notifications, if needed.</param>
- /// <param name=&#34;NotifyFunction&#34;>The pointer to the event&#39;s notification function, if any.</param>
- /// <param name=&#34;NotifyContext&#34;>
- /// The pointer to the notification function&#39;s context; corresponds to parameter
- /// Context in the notification function.
- /// </param>
- /// <param name=&#34;Event&#34;>
- /// The pointer to the newly created event if the call succeeds; undefined
- /// otherwise.
- /// </param>
- /// <returns>
- /// <see cref=&#34;EFI_STATUS.EFI_SUCCESS&#34;>EFI_SUCCESS</see>
- /// The event structure was created.<br/>
- /// <see cref=&#34;EFI_STATUS.EFI_INVALID_PARAMETER&#34;>EFI_INVALID_PARAMETER</see>
- /// One or more parameters are invalid.<br/>
- /// <see cref=&#34;EFI_STATUS.EFI_OUT_OF_RESOURCES&#34;>EFI_OUT_OF_RESOURCES</see>
- /// The event could not be allocated.<br/>
- /// </returns>
- public EFI_STATUS Invoke(
- [IN] UINT32 Type,
- [IN] EFI_TPL NotifyTpl,
- [IN] EFI_EVENT_NOTIFY NotifyFunction,
- [IN] void* NotifyContext,
- [OUT] EFI_EVENT* Event) => value__(Type, NotifyTpl, NotifyFunction, NotifyContext, Event);
- }
复制代码 但加了新语法以后,不单为提高性能创造了可能性。语言的描述能力也变强了。你看用 C# 整系统开发,IoT 啥的,方便多了吧。C# 甚至能写 UEFI 应用程序(可裸机执行)。标准 Java 连自定义值类型类都没有。Python 就更别提了。你别说是 __slots__ ,那只是引用类型的结构布局。 Python 不靠疯狂封装 C/C++ 且带点元数据的脚本语言这一身份,它也是玩不通的。
再说说领域特定这块,Java 连运算符重载都没用,直接封死,要优雅都没有资格。C# 虽然毛病多,但有运算符重载和 Linq,至少还能玩玩。Python 倒是也能玩玩,但性能就不保证了。假如你要一个 Int32 执行溢出检查,但不抛异常,只是变成特殊值。那么 C# 中,你可以巧用 Nullable<T> 的类型推导规则,可以这么玩:- // Copyright © 2020 LEI Hongfaan. Distributed under the MIT License.
- [Serializable]
- public readonly struct Int32CheckedNoThrow : IComparable, IFormattable, IConvertible
- , IComparable<Int32CheckedNoThrow>, IEquatable<Int32CheckedNoThrow> {
- private readonly Int32 Value;
- public static readonly Int32CheckedNoThrow MaxValue = 2147483647;
- public static readonly Int32CheckedNoThrow MinValue = -2147483648;
- #region Comparisons
- public static bool operator !=(Int32CheckedNoThrow first, Int32CheckedNoThrow second)
- => !(first == second);
- public static bool operator <(Int32CheckedNoThrow first, Int32CheckedNoThrow second)
- => first.Value < second.Value;
- public static bool operator <=(Int32CheckedNoThrow first, Int32CheckedNoThrow second)
- => first.Value <= second.Value;
- public static bool operator ==(Int32CheckedNoThrow first, Int32CheckedNoThrow second)
- => first.Value == second.Value;
- public static bool operator >(Int32CheckedNoThrow first, Int32CheckedNoThrow second)
- => first.Value > second.Value;
- public static bool operator >=(Int32CheckedNoThrow first, Int32CheckedNoThrow second)
- => first.Value >= second.Value;
- public int CompareTo(Int32CheckedNoThrow other) => Value.CompareTo(other.Value);
- public int CompareTo(object? obj) {
- {
- if (obj is Int32CheckedNoThrow value) {
- return CompareTo(value);
- }
- }
- {
- if (obj is Int32 value) {
- return -value.CompareTo(this); // throw
- }
- }
- return Value.CompareTo(obj); // throw
- }
- public override bool Equals(object? obj) {
- {
- if (obj is Int32CheckedNoThrow value) {
- return Equals(value);
- }
- }
- {
- if (obj is Int32 value) {
- return value.Equals(this); // throw
- }
- }
- return Value.Equals(obj); // throw
- }
- public bool Equals(Int32CheckedNoThrow other) => Value.Equals(other.Value);
- public override int GetHashCode() => Value.GetHashCode();
- #endregion Comparisons
- #region Constructors, Conversions
- internal Int32CheckedNoThrow(int value) => Value = value;
- public static implicit operator Int32(Int32CheckedNoThrow value) => value.Value;
- public static implicit operator Int32?(Int32CheckedNoThrow? value) => value?.Value;
- public static implicit operator Int32CheckedNoThrow(Int32 value) => new Int32CheckedNoThrow(value);
- public static implicit operator Int32CheckedNoThrow(Int16 value) => new Int32CheckedNoThrow(value);
- [CLSCompliant(false)]
- public static implicit operator Int32CheckedNoThrow(UInt16 value) => new Int32CheckedNoThrow(value);
- public static implicit operator Int32CheckedNoThrow(Byte value) => new Int32CheckedNoThrow(value);
- [CLSCompliant(false)]
- public static implicit operator Int32CheckedNoThrow(SByte value) => new Int32CheckedNoThrow(value);
- [CLSCompliant(false)]
- public static implicit operator Int32CheckedNoThrow(Char value) => new Int32CheckedNoThrow(value);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static implicit operator Int32CheckedNoThrow?(Int32? value) => Unsafe.As<Int32?, Int32CheckedNoThrow?>(ref value);
- public static Int32CheckedNoThrow Parse(string s, IFormatProvider? provider)
- => Int32.Parse(s, provider);
- public static Int32CheckedNoThrow Parse(string s, NumberStyles style, IFormatProvider? provider)
- => Int32.Parse(s, style, provider);
- public static Int32CheckedNoThrow Parse(string s) => Int32.Parse(s);
- public static Int32CheckedNoThrow Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null)
- => Int32.Parse(s, style, provider);
- public static Int32CheckedNoThrow Parse(string s, NumberStyles style)
- => Int32.Parse(s, style);
- public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int32CheckedNoThrow result) {
- Unsafe.SkipInit(out result);
- return Int32.TryParse(s, style, provider, out Unsafe.As<Int32CheckedNoThrow, Int32>(ref result));
- }
- public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out Int32CheckedNoThrow result) {
- Unsafe.SkipInit(out result);
- return Int32.TryParse(s, style, provider, out Unsafe.As<Int32CheckedNoThrow, Int32>(ref result));
- }
- public static bool TryParse(ReadOnlySpan<char> s, out Int32CheckedNoThrow result) {
- Unsafe.SkipInit(out result);
- return Int32.TryParse(s, out Unsafe.As<Int32CheckedNoThrow, Int32>(ref result));
- }
- public static bool TryParse([NotNullWhen(true)] string? s, out Int32CheckedNoThrow result) {
- Unsafe.SkipInit(out result);
- return Int32.TryParse(s, out Unsafe.As<Int32CheckedNoThrow, Int32>(ref result));
- }
- public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null)
- => Value.TryFormat(destination, out charsWritten, format, provider);
- public override string? ToString() => Value.ToString();
- public string ToString(string? format, IFormatProvider? formatProvider)
- => Value.ToString(format, formatProvider);
- public string ToString(IFormatProvider? provider) => Value.ToString(provider);
- public TypeCode GetTypeCode() => Value.GetTypeCode();
- bool IConvertible.ToBoolean(IFormatProvider? provider) => Value.ToBoolean(provider);
- byte IConvertible.ToByte(IFormatProvider? provider) => Value.ToByte(provider);
- char IConvertible.ToChar(IFormatProvider? provider) => Value.ToChar(provider);
- DateTime IConvertible.ToDateTime(IFormatProvider? provider) => Value.ToDateTime(provider);
- decimal IConvertible.ToDecimal(IFormatProvider? provider) => Value.ToDecimal(provider);
- Double IConvertible.ToDouble(IFormatProvider? provider) => Value.ToDouble(provider);
- Int16 IConvertible.ToInt16(IFormatProvider? provider) => Value.ToInt16(provider);
- Int32 IConvertible.ToInt32(IFormatProvider? provider) => Value.ToInt32(provider);
- Int64 IConvertible.ToInt64(IFormatProvider? provider) => Value.ToInt64(provider);
- sbyte IConvertible.ToSByte(IFormatProvider? provider) => Value.ToSByte(provider);
- Single IConvertible.ToSingle(IFormatProvider? provider) => Value.ToSingle(provider);
- string IConvertible.ToString(IFormatProvider? provider) => Value.ToString(provider);
- object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => Value.ToType(conversionType, provider);
- UInt16 IConvertible.ToUInt16(IFormatProvider? provider) => Value.ToUInt16(provider);
- UInt32 IConvertible.ToUInt32(IFormatProvider? provider) => Value.ToUInt32(provider);
- UInt64 IConvertible.ToUInt64(IFormatProvider? provider) => Value.ToUInt64(provider);
- #endregion Constructors, Conversions
- #region Arithmetic Operations
- public static Int32CheckedNoThrow? operator -(Int32CheckedNoThrow? value) {
- if (value.HasValue) {
- if (CheckedNoThrow.TryNegate(value.GetValueOrDefault(), out var result)) {
- return result;
- }
- }
- return null;
- }
- public static Int32CheckedNoThrow? operator -(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- if (CheckedNoThrow.TrySubtract(first.GetValueOrDefault(), second.GetValueOrDefault(), out var result)) {
- return result;
- }
- }
- return null;
- }
- public static Int32CheckedNoThrow? operator %(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- if (CheckedNoThrow.TryRemainder(first.GetValueOrDefault(), second.GetValueOrDefault(), out var result)) {
- return result;
- }
- }
- return null;
- }
- public static Int32CheckedNoThrow operator &(Int32CheckedNoThrow first, Int32CheckedNoThrow second)
- => first.Value & second.Value;
- // This overload is optional. Define it here to avoid CS8620 false alerms.
- public static Int32CheckedNoThrow? operator &(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- return first.GetValueOrDefault() & second.GetValueOrDefault();
- }
- return null;
- }
- public static Int32CheckedNoThrow? operator *(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- if (CheckedNoThrow.TryMultiply(first.GetValueOrDefault(), second.GetValueOrDefault(), out var result)) {
- return result;
- }
- }
- return null;
- }
- public static Int32CheckedNoThrow? operator /(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- if (CheckedNoThrow.TryDivide(first.GetValueOrDefault(), second.GetValueOrDefault(), out var result)) {
- return result;
- }
- }
- return null;
- }
- public static Int32CheckedNoThrow operator ^(Int32CheckedNoThrow first, Int32CheckedNoThrow second) {
- return first.Value ^ second.Value;
- }
- // This overload is optional. Define it here to avoid CS8620 false alerms.
- public static Int32CheckedNoThrow? operator ^(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- return first.GetValueOrDefault() ^ second.GetValueOrDefault();
- }
- return null;
- }
- public static Int32CheckedNoThrow operator |(Int32CheckedNoThrow first, Int32CheckedNoThrow second) {
- return first.Value | second.Value;
- }
- // This overload is optional. Define it here to avoid CS8620 false alerms.
- public static Int32CheckedNoThrow? operator |(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- return first.GetValueOrDefault() | second.GetValueOrDefault();
- }
- return null;
- }
- public static Int32CheckedNoThrow operator ~(Int32CheckedNoThrow value) {
- return ~value.Value;
- }
- public static Int32CheckedNoThrow operator +(Int32CheckedNoThrow value) {
- return value;
- }
- public static Int32CheckedNoThrow? operator +(Int32CheckedNoThrow? value) {
- return value;
- }
- public static Int32CheckedNoThrow? operator +(Int32CheckedNoThrow? first, Int32CheckedNoThrow? second) {
- if (first.HasValue && second.HasValue) {
- if (CheckedNoThrow.TryAdd(first.GetValueOrDefault(), second.GetValueOrDefault(), out var result)) {
- return result;
- }
- }
- return null;
- }
- public static Int32CheckedNoThrow operator <<(Int32CheckedNoThrow value, int count)
- => value.Value << count;
- public static Int32CheckedNoThrow? operator <<(Int32CheckedNoThrow value, int? count)
- => value.Value << count;
- // This overload is optional. Define it here to avoid CS8620 false alerms.
- // This overload also suppresses CS8629.
- public static Int32CheckedNoThrow? operator <<(Int32CheckedNoThrow? value, int? count) {
- if (value.HasValue && count.HasValue) {
- return value.GetValueOrDefault() << count.GetValueOrDefault();
- }
- return null;
- }
- public static Int32CheckedNoThrow operator >>(Int32CheckedNoThrow value, int count)
- => value.Value >> count;
- public static Int32CheckedNoThrow? operator >>(Int32CheckedNoThrow value, int? count)
- => value.Value >> count;
- // This overload is optional. Define it here to avoid CS8620 false alerms.
- // This overload also suppresses CS8629.
- public static Int32CheckedNoThrow? operator >>(Int32CheckedNoThrow? value, int? count) {
- if (value.HasValue && count.HasValue) {
- return value.GetValueOrDefault() >> count.GetValueOrDefault();
- }
- return null;
- }
- #endregion Arithmetic Operations
- }
复制代码 用起来就和语言自带的 int 几乎一致。Java 遇到类似问题可就傻眼了。你看看 Java 操作无符号整数就知道了,遑论其他。
再说一个,lambda 函数的类型推导也是。题主是要把,只能到处插入类型转换的 Java,也称为语法上优雅么?那我还是别优雅了。 |